Escape analysis: stack when provable, heap when not
The motivation: heap allocation costs, stack allocation is nearly free. A heap allocation is slower and creates work for the garbage collector; a stack allocation is just moving the stack pointer, and the memory vanishes when the function returns at zero cost. So the question for every variable is whether it can live on the stack alone. It can, but only if it never outlives its function. Once it might live longer, it must go on the heap.
Escape analysis is the compiler's static proof at compile time. The compiler analyzes where each variable goes and proves whether it ever leaves its function. If the proof says it never leaves, the variable is allocated on the stack. If the proof fails, or the variable clearly leaves, it goes on the heap. The same source code can therefore allocate differently depending on what the compiler can prove, and the programmer writes nothing special to control it.
The typical escapes. A variable escapes when it is returned from its function, as in returning a pointer to a local; when it is stored into something that lives longer, such as a global, a map, a slice, or a heap object; when it is captured by a closure that outlives the function; when it is passed to an interface, whose dynamic type the compiler cannot see through; or when it is simply too large for the stack.
How to see the compiler's verdict. Build with go build -gcflags=-m and the compiler prints, per variable, whether it moved to heap and why it escaped. That output is the way to check an allocation hypothesis instead of guessing.
Production note. Escape analysis is a reason to write straightforward code and check with -m plus a benchmark when allocation actually shows up in a profile, not a reason to contort code in advance. The compiler's proof, not the programmer's guess, decides where a variable lives.