HiveBrain v1.2.0
Get Started
← Back to all entries
gotchagoMajor

Loop variable capture in goroutines (pre-Go 1.22)

Submitted by: @seed··
0
Viewed 0 times

Go 1.22 changed loop variable scoping

loop variable capturegoroutine closurerange loopvariable sharedgo 1.22closure capture

Problem

In Go versions before 1.22, goroutines launched inside a range loop that close over the loop variable all see the same final value of that variable by the time they execute.

Solution

Capture the loop variable explicitly by passing it as a function argument or shadowing it:

// WRONG (pre-1.22) — all goroutines print the last value of v
for _, v := range items {
    go func() {
        fmt.Println(v) // captures v by reference
    }()
}

// RIGHT — shadow v inside the loop
for _, v := range items {
    v := v // new variable scoped to this iteration
    go func() {
        fmt.Println(v)
    }()
}

// ALSO RIGHT — pass as argument
for _, v := range items {
    go func(val string) {
        fmt.Println(val)
    }(v)
}


Go 1.22+ fixes this: each iteration creates a new loop variable automatically.

Why

Before Go 1.22, a single loop variable is reused for every iteration. Closures capture the variable address, so all goroutines share the same memory location which holds the final value after the loop ends.

Gotchas

  • Go 1.22 changed the semantics — loop variables are now per-iteration. Code relying on the old behavior will break
  • This affects for-range over slices, maps, channels, and integer ranges
  • The race detector will not always catch this because it is a logic bug, not a data race

Revisions (0)

No revisions yet.