In Go, the for loop construct can include an initialization block (init), condition check, and a postfix expression. Historically, this mechanism was created for the convenience of writing code and the familiarity of C-like languages. However, in Go, the scope of the loop variable (i) has specific characteristics that significantly affect its behavior inside nested functions, closures, and goroutines.
Problem — When running goroutines or closures on each iteration of the loop, unexpected behavior often occurs: the variable i is not copied but "captured" by reference, meaning the closure refers to a shared loop variable, which takes the last value after the loop ends. This leads to the same result across all goroutines/closures, even though the logic might have suggested otherwise.
Solution — If you need to pass the value of the variable from each iteration, use explicit copying of the variable (via an additional variable) or pass it as an argument to the closure.
Code example:
for i := 0; i < 3; i++ { go func(j int) { fmt.Println(j) }(i) // Correct! Copied value } for i := 0; i < 3; i++ { go func() { fmt.Println(i) }() // Error: all goroutines will print 3 }
Key features:
Does the scope of the for variable change when using break or continue?
No. The scope of the variable declared in for is always limited to the block of that loop. Break or continue only interrupts the current iteration but does not "propagate" the variable outside.
Can a variable declared in the init part of for be captured within a method outside the loop?
No. The variable is only visible within the for loop itself and all nested blocks but not outside it after the loop exits.
What happens if the variable is captured in a defer expression inside for?
The same situation: the defer function will "see" not the value at the time of creation but the current value of the variable at the time of executing defer (usually the last value of the loop).
for i := 0; i < 3; i++ { defer fmt.Println(i) // all defer will print 3 }
In a Go web server, a developer launched several goroutines to handle different ports, using the port index as the loop variable and capturing it directly in a lambda expression. All goroutines accessed one port — the last one in the array.
Pros:
Cons:
The team established a rule — always copy the loop variable's value into a new variable that the closure/goroutine will capture.
Pros:
Cons: