Lesson 25 / 25

A Go Concurrency Checklist

Review questions before shipping concurrent code.

Questions to ask in review

For every go statement: how does it stop, and who waits for it? For every channel: who owns and closes it, is the buffer size justified, and can every send and receive observe cancellation? Are functions that block taking ctx context.Context as the first parameter, and is every cancel called? Is shared state guarded by one clearly documented mechanism (a mutex next to the fields, or a single owning goroutine), and are mutexes never copied? Is concurrency bounded with a pool, semaphore or SetLimit, and are external calls rate-limited? Are errors collected (errgroup) rather than dropped? Does the service shut down gracefully within a deadline? Do tests run with -race, check for leaks and avoid sleeps?

The checklist

Use it in code reviews.

[ ] every goroutine has an exit path and an owner that waits for it
[ ] no time.Sleep used for synchronisation
[ ] only the sender closes a channel; directional types in APIs
[ ] buffer sizes are justified (default: unbuffered or 1)
[ ] every blocking send/receive also selects on ctx.Done()
[ ] ctx is the first parameter, never stored in structs; cancel() always called
[ ] timers/tickers stopped; no time.After for loop-wide timeouts
[ ] shared state guarded by a mutex next to its fields, or owned by one goroutine
[ ] mutexes and WaitGroups passed by pointer, never copied (go vet)
[ ] concurrency bounded (worker pool, semaphore, errgroup.SetLimit)
[ ] external calls rate-limited (x/time/rate)
[ ] errors collected with errgroup; partial results handled
[ ] graceful shutdown: signal.NotifyContext + Server.Shutdown with a deadline
[ ] go test -race ./... in CI; goleak in tests; goroutine count monitored

Prefer boring concurrency

The best concurrent code looks simple: a few well-named goroutines, clear ownership and standard building blocks such as errgroup and context. If a design needs a diagram to explain who closes which channel, simplify it.

Quick check: Which item belongs on a Go concurrency review checklist?

  • Store the request context in the service struct
  • Use large channel buffers to avoid deadlocks
  • Every goroutine has a defined way to stop and someone waiting for it
  • Use time.Sleep in tests to let goroutines finish
Answer

Every goroutine has a defined way to stop and someone waiting for it — Clear goroutine lifetimes prevent leaks, races at exit and lost work.