Skip to content
Performance and Escape Analysis
step 1/7

Reading — step 1 of 7

Learn

~3 min readErrors, Testing, and Performance

Go's GC is fast, but unnecessary heap allocations still hurt — they pressure the GC, blow caches, and run slower than stack memory. The compiler decides whether each value lives on the stack or the heap; understanding escape analysis lets you write code that stays on the stack.

Stack vs heap in Go

  • Stack allocations are nearly free — bump a pointer, zero on return
  • Heap allocations cost: GC tracking, atomic alloc paths, cache misses, eventual GC sweep
  • The compiler chooses for you, based on whether a value's pointer escapes the function

Seeing escape decisions

go build -gcflags="-m -l" ./...

Produces output like:

./main.go:8:9: &User{...} escapes to heap
./main.go:14:13: leaking param: x
./main.go:18:6: moved to heap: result

-m shows the compiler's escape and inlining decisions. -l disables inlining (clearer output for diagnosis).

What causes escape

A value escapes if its pointer leaves the function. Common patterns:

go

Common allocation sources to watch

  • interface{} parameters and returns — the value is boxed (heap)
  • fmt.Sprintf for hot paths — allocates a new string each call
  • append with insufficient capacity — every grow re-allocates the backing array
  • Maps with growing keys — internal restructuring allocates
  • Defer with closures — each defer frame is an allocation in older Go versions (mostly fixed in 1.14+)
  • Strings + concatenation — each + produces a new string

Pre-allocate slices

go

Goes from O(log n) reallocations to zero. Big win for hot paths.

strings.Builder for concatenation

go

For long strings, strings.Builder is dramatically faster.

sync.Pool for high-frequency reuse

go

Recycles buffers between requests. Standard pattern in HTTP middleware, JSON encoders.

Profiling

go test -cpuprofile=cpu.prof -memprofile=mem.prof -bench=.

Then go tool pprof cpu.prof opens an interactive profiler. (pprof) top shows the hottest functions; (pprof) list FuncName shows source-annotated time. Production servers expose /debug/pprof/ via net/http/pprof.

When to optimize

Go's GC is fast. The vast majority of code doesn't need allocation analysis. Reach for it when:

  • Profiling has identified a real hot path
  • The code runs at >1k QPS
  • Latency p99 matters (GC pauses)

Premature optimization in Go is the same as in any language — measure first, optimize second.

Common mistakes

  • Optimizing without profiling — the bottleneck is rarely where you think.
  • Over-using sync.Pool — adds complexity. Worth it only when allocation is genuinely the bottleneck.
  • Caring about escape for cold paths — only matters in hot loops; cold code doesn't move the needle.
  • Trusting microbenchmarks too much — Go's compiler can optimize away unused results. Use the result with b.Log or assign to a global.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…