Golang / Golang Internals and Memory Management Interview Questions
How do closures capture variables in Go and what is the classic goroutine loop bug?
A closure in Go captures variables by reference — it holds a pointer to the outer variable, not a copy of its value at the time of closure creation. This means if the variable changes after the closure is created but before it executes, the closure sees the new value.
// ââ Classic goroutine loop bug ââ // Go 1.21 and earlier: for i := 0; i < 3; i++ { go func() { fmt.Println(i) // captures &i â all goroutines print 3, 3, 3 }() } time.Sleep(time.Second) // Fix 1: pass as argument (creates a copy per iteration) for i := 0; i < 3; i++ { go func(i int) { // i is now a local copy fmt.Println(i) // 0, 1, 2 (in some order) }(i) } // Fix 2: shadow the variable inside the loop (pre-Go 1.22) for i := 0; i < 3; i++ { i := i // new i per iteration go func() { fmt.Println(i) }() } // Go 1.22+ fix: loop variables are per-iteration by default // The behaviour changed â each loop iteration now has its own i // So the bug no longer exists in Go 1.22+ for range loops // Closures in non-goroutine contexts adders := make([]func() int, 3) for i := 0; i < 3; i++ { i := i // shadow is needed pre-Go-1.22 adders[i] = func() int { return i + 10 } } fmt.Println(adders[0](), adders[1](), adders[2]()) // 10 11 12
Go 1.22 loop variable change: starting in Go 1.22, the loop variable in a for range loop is declared fresh each iteration (similar to JavaScript's let). This silently fixes the classic goroutine loop bug for range loops in new code. Classic three-clause for i := 0; ... loops also got the fix in 1.22. Code that depended on the old behaviour (sharing across iterations) may break.
More Related questions...