Go

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. sum still needs the mutex, or use atomic.Int64.
  • The loopclosure vet check was relaxed after 1.22.

More on Goroutines & the Scheduler

All 35 Goroutines & the Scheduler questions