Goroutine leaks represent the most insidious form of memory degradation in Go backend systems. Unlike unreferenced heap objects, a suspended goroutine retains its entire stack frame, active pointers, and referenced buffers indefinitely, making it completely invisible to garbage collector sweeps.

1. Common Goroutine Leak Anti-Patterns

A goroutine leaks whenever it is blocked waiting for an event that will never materialize:

  • Writing to an unbuffered channel whose receiving party exited prematurely due to an error.
  • Reading from an unclosed channel where the producer crashed without signaling EOF.
  • Performing unbounded network I/O lacking strict timeout deadlines.
leak_prevention.go
// ANTI-PATTERN: Leaks a goroutine on slow downstream
func UnsafeQuery() string {
	ch := make(chan string) // Unbuffered
	go func() {
		res := callExternalService()
		ch <- res // BLOCKS FOREVER if caller times out!
	}()
	select {
	case r := <-ch:
		return r
	case <-time.After(200 * time.Millisecond):
		return "timeout"
	}
}

// PRODUCTION-SAFE: Buffered channel + Context Cancellation
func SafeQuery(ctx context.Context) (string, error) {
	ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
	defer cancel()

	ch := make(chan string, 1) // Buffer size 1 prevents block
	go func() {
		res := callExternalService()
		select {
		case ch <- res:
		case <-ctx.Done():
		}
	}()

	select {
	case <-ctx.Done():
		return "", ctx.Err()
	case r := <-ch:
		return r, nil
	}
}
Advertisement / Sponsored