Classic Range Gotcha With Loop Variables (pre
Fast recall
Quick revision
Core answer
Practical understanding of Range Over Slice Map.
Repeating a definition without saying when to use it.
Give one trade-off and say how you would test it.
Interview-ready guide
Interview answer
The bug every Go codebase of a certain age contains:
// Go 1.21 and earlier
for _, task := range tasks {
go func() {
process(task) // captures the SHARED task variable
}()
}
// all goroutines likely process the LAST task — by the time they run,
// task has been reassigned to the final iteration's valueThe cause: pre-1.22, the loop variable was a single memory location reused across iterations. The closure captured that location, not the value — so when goroutines finally executed, they all read whatever the loop left in it: the last value.
The old fixes were workarounds: pass it in (go func(t Task) { process(t) }(task)) or shadow it (task := task inside the loop).
Go 1.22 changed the semantics: each iteration gets a fresh variable. The closure-capture bug simply disappears on 1.22+ — capturing the loop variable now captures that iteration's value. This is a language change, not a runtime optimization, so the answer must be version-scoped: on 1.22+, the gotcha is history; on older code you still meet it, and the pass-it-in fix is still the right pattern for clarity.
Classic Range Gotcha With Loop Variables (pre
Direct Answer
Before 1.22, the loop variable was ONE reused variable — closures and goroutines capturing it saw the last value. 1.22 gives each iteration a fresh variable.
Deep Dive & Architecture
Why did the old semantics exist at all? Performance — one variable, one address, reused: the loop didn't allocate per iteration. The cost was that the single-address decision leaked into closures, and closures capture by reference in Go. The two features composed into the most common Go bug of the 2010s.
The 1.22 fix is a rare breaking-ish change the Go team could make because the old behavior was almost never intended — the proposals (years of them) documented that the capture bug dominated every legitimate use of the shared variable. The compatibility promise bent to reality: code relying on the old behavior was almost certainly broken code.
For interviews and code review today: mention the version. 'Each iteration gets a fresh variable since 1.22' is the current truth; 'capture the loop variable in a goroutine and you'll see the last value' is the historical truth that explains why old code is full of pass-through parameters and why linters still flag the pattern. Both halves are knowledge; the version boundary is where they meet.
Common Mistakes & Pitfalls
Repeating a definition without saying when to use it.
Check your understanding
Answer these without looking back at the article.
- What is Range Over Slice Map, and when would you use it?
- When would you use Range Over Slice Map in a real project?
- What is a common mistake when using Range Over Slice Map?
Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.