Stack Growth and Copying
Stack Growth and Copying
Overview
Each goroutine starts with a small stack (on the order of a few KB historically; exact size is version-dependent) and grows by allocating a larger stack and copying live frames when needed. Stacks also shrink when underutilized.
This design makes millions of Gs feasible — you do not pre-reserve 1–8 MiB per goroutine like many OS threads.
Diagram: Stack growth
G stack nearly full
│
v
morestack → larger stack
│
v
copy frames + fix pointers → resume
Growth Path
G running, stack nearly full
-> morestack
-> allocate larger stack
-> copy stack contents
-> adjust pointers into stack (compiler/runtime)
-> resume
Go forbids taking the address of a stack variable and using it after the frame dies; the compiler and escape analysis keep GC and stack copy correct.
Why Deep Recursion Hurts
Deep recursion causes repeated growth and large stacks:
func sum(n int) int {
if n == 0 {
return 0
}
return n + sum(n-1) // grows stack with depth
}Prefer iterative algorithms for unbounded depth.
cgo and Stacks
cgo may switch to larger system stacks for C execution. Crossing Go↔︎C frequently is expensive (scheduler + stack). See cgo transitions.
Debugging Stacks
GOTRACEBACK=all ./app
// panic dumps all Gsbuf := make([]byte, 1<<16)
n := runtime.Stack(buf, true) // all goroutines if true
fmt.Printf("%s", buf[:n])Nosplit
//go:nosplit
func tight() { /* cannot grow stack; for runtime/asm constraints */ }User code almost never needs //go:nosplit. Misuse panics if the frame does not fit.
Experiment
go mod init example
go run .package main
import (
"fmt"
"runtime"
)
func depth(n int, max int) int {
if n == max {
var st runtime.StackRecord
// approximate: print goroutine stack size indirectly via Stack
buf := make([]byte, 64*1024)
k := runtime.Stack(buf, false)
fmt.Printf("at depth %d stack dump bytes=%d\n", n, k)
return n
}
return depth(n+1, max)
}
func main() {
fmt.Println("GOMAXPROCS", runtime.GOMAXPROCS(0))
_ = depth(0, 200)
}What to notice: Deep call chains produce large stack dumps; production code should bound recursion and prefer workers with small frames.
Try next: Compare a recursive tree walk vs explicit stack slice for a large tree — RSS and latency under load.