Three Go Concurrency Mistakes I See in Almost Every Worker Pool

Go makes concurrency feel easy — goroutines are cheap, channels are built in, and you can spin up a worker pool in under 30 lines. But that same ease is why subtle bugs slip through: the code compiles, tests pass, and the problem only shows up under load.

After years on high-throughput Go backends, I keep running into the same three:

:one: Goroutine leaks from channels that never close. If the producer stops sending but never calls close(), your workers block forever on for range — quietly holding memory. Fix: the producer owns the channel and closes it with defer close().

:two: Calling wg.Add inside the goroutine. Do that and Wait() can hit zero before any worker registers, so main exits while work is still running — dropped silently. Fix: call wg.Add before launching the goroutine (or Add(N) up front).

:three: Bidirectional channels everywhere. Plain chan Job lets a worker accidentally write to the input channel and compile clean. Fix: use chan<- and <-chan at function boundaries — free, compiler-enforced documentation.

None of these need clever solutions. They need habit: producer owns close, Add precedes launch, direction is documentation.

Bonus: Go 1.25’s go vet now flags mistake #2 automatically.

Full write-up here https://medium.com/stackademic/three-go-concurrency-mistakes-i-see-in-almost-every-worker-pool-480a7de51c43

#Golang #Concurrency #BackendDevelopment #SoftwareEngineering