What does this print? (Closure capture and the Go 1.22 loop variable change)
Question 168MediumGo 1.22 to 1.25
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
var mu sync.Mutex
sum := 0
for i := 0; i < 3; i++ {
wg.Add(1)
go func() {
defer wg.Done()
mu.Lock()
sum += i
mu.Unlock()
}()
}
wg.Wait()
fmt.Println(sum)
}
With go 1.22 or later in go.mod, it prints 3 (0+1+2). Every iteration has its own i, so each closure captures a different variable.
With an older go directive, all closures share one i. The result depends on timing: often 9 (3+3+3), anything from 3 to 9 is possible (each goroutine reads i no earlier than its own iteration), and there is a data race on i. The old fix was i := i or passing i as an argument.
What the interviewer wants to hear:
- The behaviour depends on the module's go version, not on the toolchain you build with.
- The loop variable fix does not protect variables declared outside the loop.
sumstill needs the mutex, or useatomic.Int64. - The
loopclosurevet check was relaxed after 1.22.
More on Goroutines & the Scheduler
- Q166What happens when main returns while other goroutines are still running? What does this print?
- Q167What happens if a goroutine panics? Can the parent recover it?
- Q169When does the Go runtime report "all goroutines are asleep - deadlock!" and when doesn't it?
- Q170Concurrency vs. parallelism in Go — can goroutines run in parallel with GOMAXPROCS=1?
- Q171What are the goroutine states and what transitions happen on a channel block?
- Q172How can you observe the scheduler? (schedtrace, go tool trace, pprof)