Skip to content

Go — Theory

  • G = goroutine (user-space).
  • M = OS thread (machine).
  • P = processor (logical context, holds runnable goroutine queue). Count = GOMAXPROCS (default = CPU count).
  • M:N scheduling — many G’s mapped onto few M’s via P.
  • Each P has local run queue; global queue for overflow. Work-stealing between P’s.
  • Goroutine starts ~2KB stack, grows by copying.
  • Preemption: cooperative + async since 1.14 (signal-based at safe points). Long tight loops can starve scheduler pre-1.14.
  • Unbuffered (make(chan T)): rendezvous — sender blocks until receiver ready (and vice versa).
  • Buffered (make(chan T, n)): send blocks when full, receive blocks when empty.
  • Close: signals “no more values”. Receive from closed channel returns zero value + ok=false.
  • Don’t close from receiver side. Close from single sender or via dedicated done channel.
  • Panic: send on closed channel, double close.
  • Happens-before relationships: channel send happens before corresponding receive completes; mutex unlock happens before subsequent lock; goroutine start happens after go f() line; goroutine completion has no general happens-before to caller (use sync).
  • Run with -race flag in test/dev to detect data races.
  • Atomic ops in sync/atomic provide ordering guarantees.
NeedTool
protect map/slicesync.Mutex
read-heavy shared statesync.RWMutex
count / flagsync/atomic
communicate workchannel
wait for goroutinessync.WaitGroup
run-once initsync.Once
bounded parallelismsemaphore via buffered chan or golang.org/x/sync/semaphore
temporary objects (pool)sync.Pool
  • Channel: when ownership transfers (data passed between goroutines), or when signaling. “Don’t communicate by sharing memory; share memory by communicating.”
  • Mutex: when protecting access to shared state in place. Often simpler/faster for counters and caches.
  • Atomics are orders of magnitude faster than channels for simple counters.
  1. Goroutine leak scenarios: blocked send on full channel, blocked receive on never-closed channel, missing context cancellation. Always have an exit path.
  2. Why is nil channel useful? Receive/send on nil blocks forever — use in select to disable a case dynamically.
  3. for range on channel stops when channel is closed.
  4. Pointer vs value receiver consistency: don’t mix on same type. If any method needs pointer, all should be pointer.
  5. Interface holding nil pointer: classic gotcha — var err *MyErr = nil; var e error = err; e != nil is true. Interface stores (type, value); only nil-interface has both nil.
  6. Slice append surprises: append(s, x) may or may not modify underlying array depending on capacity. Always reassign: s = append(s, x).
  7. defer evaluation: args evaluated immediately, function runs at return. LIFO order.
  8. select with default: non-blocking send/receive. Without default, blocks until any case ready.
  • Concurrent, tri-color mark-sweep with write barriers.
  • Goal: low latency (<1ms STW typically). Tunable via GOGC (% of live heap that triggers next GC; default 100).
  • runtime/debug.SetMemoryLimit (1.19+) for soft cap.
  • Escape analysis: compiler decides heap vs stack. go build -gcflags='-m' shows decisions.
  • Avoid unnecessary allocs: reuse buffers, sync.Pool for hot paths, string builder, preallocate slices.
  • pprof for CPU/mem/goroutine/block/mutex profiles.
  • Benchmark: go test -bench=. -benchmem.
  • Errors are values. Compose via wrapping (%w).
  • Sentinel errors (var ErrNotFound = errors.New("not found")) checked with errors.Is.
  • Custom error types checked with errors.As.
  • Don’t ignore errors — _ = err is a smell.
  • Worker pool: N goroutines reading jobs from chan, sending results to result chan.
  • Fan-out/fan-in: distribute work, merge results.
  • Pipeline: stages connected by channels. Use context for cancellation.
  • Rate limiting: time.Tick or golang.org/x/time/rate.Limiter.
  • Singleflight (golang.org/x/sync/singleflight): dedupe concurrent identical calls.
  • errgroup (golang.org/x/sync/errgroup): goroutines with cancellation on first error.
  • testing stdlib. *testing.T for tests. Subtests via t.Run.
  • Table-driven tests idiomatic.
  • httptest for HTTP handlers. testcontainers-go for real DBs.
  • Race detector: go test -race.
  • Fuzzing: go test -fuzz=Fuzz (1.18+).