A pointer does not automatically mean a heap allocation. In Go, the compiler chooses between a goroutine’s stack and the heap according to whether a value can safely remain in a stack frame. Each goroutine has its own stack; goroutines share the heap. Heap allocation is slower than stack allocation and leaves memory for the garbage collector (GC) to reclaim. section 12.5.1 section 12.5.2 section 12.11
A function call creates a stack frame. After the function returns, its former local addresses cannot safely serve a caller’s references: the stack space can be reused. If the compiler cannot prove that a local value will stop being referenced at return, it allocates that value on the heap instead. section 12.5.1 section 12.5.2
func main() {
a := 3
b := 2
c := sumPtr(a, b)
println(*c)
}
//go:noinline
func sumPtr(x, y int) *int { #1
z := x + y
return &z
}
In this pointer-returning example, the caller receives the address of z. Returning that address does not leave a dangling pointer: z escapes its function frame and is allocated on the heap. section 12.5.1 section 12.5.2
Measure the cost of returning an address
The supplied benchmarks contrast returning a sum as an int with returning a pointer to the sum. The value-returning version reports 0 B/op and 0 allocs/op; the pointer-returning version reports 8 B/op and 1 alloc/op. Their reported times are 1.261 ns/op and 14.84 ns/op, respectively. These results illustrate this example, not a rule that every pointer is expensive. section 12.5.1
Direction matters. Passing pointers to a called function can leave the pointed-to values on the caller’s stack: those addresses remain valid during the call. Returning a pointer to a local value is different because the caller may use it after the callee returns. Thus, avoid treating “uses a pointer” as a diagnosis; inspect where the value can be referenced. section 12.5.2
Other situations that may put a value on the heap include:
- A global variable, a pointer sent through a channel, or a value sent through a channel that contains a reference to a local variable. section 12.5.2
- A local value too large for the stack, or one whose size is unknown to the compiler, such as a slice made with a variable length. section 12.5.2
- A slice backing array reallocated by append. section 12.5.2
$ go build -gcflags "-m=2" ... ./main.go:12:2: z escapes to heap:
Check a suspected escape with go build -gcflags "-m=2". The compiler output in this example reports that z escapes to the heap. The listed escape patterns are not exhaustive and can change across Go versions, so use the diagnostic rather than relying on a blanket rule. section 12.5.2
Heap use also affects GC work. Go’s concurrent mark-and-sweep collector identifies heap objects still in use and reclaims unreferenced memory. GOGC sets a heap-growth percentage for triggering another cycle: at its stated default of 100%, a 128 MB heap after one GC would reach 256 MB before the next growth-triggered cycle. After an allocation peak, a periodic scavenger can return unneeded memory to the operating system. Reduce allocations where measurement shows a worthwhile benefit, rather than making clear code more complex without evidence of a bottleneck. section 12.9.1 section 12.0






