Lesson 24 · Senior Go backend engineering

Escape analysis: stack vs heap

Why some values stay cheap and others become GC work.

Your win: explain escape analysis in interview language, recognise the most common escape patterns, and connect heap allocation to the backend costs that actually matter: GC pressure, latency, and memory growth under load.

In plain English Imagine the compiler asking one question about every value: “Can this safely die when the current function returns?” If yes, the value can stay on the stack. If no, it must move to the heap so something longer-lived can still reach it.

Why senior engineers care

At beginner level, it is enough to know that Go manages memory for you. At senior level, the next question is: what runtime work did my code create? Heap allocation is not a bug, but it usually means more work for the garbage collector — and on a busy service, that can show up as more memory use and more latency variance.

The mental model Stack = cheap and local. Heap = flexible but more expensive. Escape analysis is the compiler deciding which bucket each value belongs in.

The two-memory story

Stack

Lives with the current call. Very cheap. Great for short-lived values that do not escape their frame.

Heap

Lives beyond one call when necessary. Flexible, but now the runtime must track and eventually reclaim it.

func good() int {
    x := 42
    return x           // usually stays on the stack
}

func escaping() *int {
    x := 42
    return &x          // pointer outlives the frame → heap escape
}

The common ways values escape

Backend use case In this repo, the hottest code is not usually controller glue — it is shared library code, queue-processing loops, adapters, serialization, and retry/fan-out paths in internal/golibs. That is where allocation awareness pays off, because a small per-item cost gets multiplied by throughput.
Common mistake Treating escape analysis like a purity contest. The goal is not “zero heap allocations forever.” The goal is to understand which allocations are intentional, and to notice accidental heap churn on hot paths after measurement shows it matters.

How to inspect it

go build -gcflags="-m" ./path/to/package

The compiler will print clues such as moved to heap. Read that output as a hint about runtime cost, not as an order to micro-optimize everything you see.

Read this next

GC guide + diagnostics

The GC guide gives the big-picture cost model. The diagnostics docs help you observe what your code is actually doing on this machine today.

go.dev/doc/gc-guide
go.dev/doc/diagnostics

Check yourself (from memory)

Q1. A value usually escapes when…

Escape is mainly about lifetime, not about one magic size threshold.

Q2. Why do senior engineers care about escapes?

More heap churn often means more runtime work later.
Give the one-sentence senior explanation of escape analysis.
recall, then click to reveal
The compiler decides whether a value can safely die with the current stack frame or must move to the heap because something may outlive that frame.
Want a live walkthrough of one repo function with -gcflags="-m" output and a plain-English explanation of each escape? Ask me.

Sources. A Guide to the Go Garbage Collector; Go Diagnostics.