The Go compiler implements a mechanism called escape analysis: all values are placed on the stack by default, but if a variable "escapes" its scope (for example, if a pointer to a local variable is returned), it is automatically placed on the heap.
This is important for performance:
The compiler tries to determine whether an object can be placed on the stack, but if it implicitly “survives” the function boundaries — it is put on the heap.
Example with escape:
func NewPoint() *int { a := 42 return &a // escape to heap! }
Example without escape:
func Sum(a, b int) int { c := a + b return c // on the stack }
For compiler diagnostics, use go build -gcflags='-m' to see details:
go build -gcflags='-m' main.go
"Will an object go to the heap if only its value — not a pointer — is returned from the function?"
It is often assumed that any returned values "go to the heap." In fact, if a value (not a pointer) is returned, the variable may remain on the stack.
Example:
func F() int { x := 10 return x // stack allocation, does not go to heap }
Story
During mass creation of structures via a factory function returning pointers to local variables, memory consumption and GC load sharply increased. It turned out that all objects went to the heap due to returning a pointer to a local variable, although this could have been avoided by returning the value.
Story
In a microservice, due to passing pointers between goroutines and returning them from different functions, a large number of objects “leaked” from the stack to the heap, slowing down performance and causing frequent GC pauses.
Story
A developer accidentally wrapped a static array in a slice inside a function, returned a pointer to the slice — and during load testing, began to catch memory leaks. Diagnostics showed that the variable did not “escape” the function, but Go mistakenly decided to place it in the heap due to indirect usage.