List the common reasons a value escapes to the heap.
Question 333HardGo 1.22 to 1.25
- Returning or storing its address somewhere that outlives the frame: a global, a heap object, or a channel send of a pointer.
- Interface conversion, where a non-pointer value is boxed into
any.fmt.Println(x)is the classic case. Small integers 0-255, zero values and constants can use static data and avoid the allocation. - Closures that capture variables by reference and outlive the frame, such as
go func(){...}()or returned funcs. - Sizes unknown at compile time:
make([]T, n)with a non-constantn. Go 1.25 can put small variable-sized backing arrays on the stack, but you should not rely on it. - Values that are too large. Explicitly declared variables over 128 KB, or implicit allocations (
new,&T{},make) over 64 KB, go to the heap. The explicit limit was 10 MB in older releases. - Calls the compiler cannot analyse: arguments passed through interface method calls or function values are assumed to escape.
- Slices of pointers or maps whose elements are pointers to locals.
type Logger interface{ Log(v any) }
func work(l Logger) {
buf := make([]byte, 64) // constant size, would stay on the stack...
l.Log(buf) // ...but a dynamic call through an interface makes buf escape
}
What the interviewer is looking for: interfaces and indirect calls are the most common hidden source of allocations in hot paths. You find them by profiling and reading -m output, not by guessing.
More on Memory, GC & Runtime Internals
- Q331How does Go decide whether a variable lives on the stack or the heap?
- Q332How do you inspect escape analysis decisions, and how do you read the output?
- Q334How do goroutine stacks grow, and why does that constrain the language?
- Q335Describe Go's memory allocator: size classes, mcache/mcentral/mheap, and the tiny allocator.
- Q336Explain Go's concurrent tri-color mark-and-sweep garbage collector.
- Q337What is a write barrier and which one does Go use?