How would you detect and diagnose a goroutine leak?
Question 372MediumGo 1.22 to 1.25
Symptom: runtime.NumGoroutine() (export it as a metric) grows monotonically along with memory. Diagnose with the goroutine profile:
curl -s 'http://127.0.0.1:6060/debug/pprof/goroutine?debug=1' | head -50
# debug=1: grouped by identical stack with counts
# debug=2: every goroutine, full stack, with wait reason and duration
go tool pprof -http=:8080 http://127.0.0.1:6060/debug/pprof/goroutine
Look for a large count parked on the same line, e.g. chan send or select for "35 minutes". Classic culprit:
func firstResult(ctx context.Context, urls []string) string {
ch := make(chan string) // BUG: unbuffered; losers block forever
for _, u := range urls {
go func() { ch <- fetch(u) }()
}
return <-ch
}
// Fix: make(chan string, len(urls)), or select on ctx.Done() in the sender.
In tests, catch leaks with go.uber.org/goleak (defer goleak.VerifyNone(t) or in TestMain), or with testing/synctest, which fails if goroutines in the bubble are left deadlocked. Go 1.26 also adds an experimental goroutineleak profile that uses the GC to find goroutines blocked on unreachable channels. Interviewer wants: every goroutine must have a clear exit path — context cancellation, closed channel, or buffered send.
More on Performance, Profiling & Testing
- Q370How does the Go CPU profiler work, and what is the difference between "flat" and "cum" in pprof output?
- Q371Explain inuse_space vs alloc_space in a heap profile. Which do you use to find a memory leak vs GC pressure?
- Q373What do the block and mutex profiles measure, how do you enable them, and how do they differ?
- Q374When would you use go tool trace instead of pprof? What can it show that profiles cannot?
- Q375What is testing.B.Loop (Go 1.24) and why is it preferred over the classic b.N loop?
- Q376What does this benchmark report, and why is it wrong?