Go gotchas - loop variable and goroutines

Another Golang issue with goroutines and for loops today :) This time let’s assume we start with a simple for loop that calls an anonymous function:

package main

import (
	"fmt"
	"sync"
)

func main() {
	numbers := []int{1, 2, 3, 4, 5, 6}
	// WaitGroup will be used to wait for child goroutines
	var wg sync.WaitGroup
	for _, n := range numbers {
		wg.Add(1)
		func foo() {
			fmt.Printf("%d ", n)
			wg.Done()
		}()
	}
	wg.Wait()
}
This works fine and prints

1 2 3 4 5 6

but to run the anonymous function in child goroutines - we will add a go keyword before the function call:

package main

import (
	"fmt"
	"sync"
)

func main() {
	numbers := []int{1, 2, 3, 4, 5, 6}
	// WaitGroup will be used to wait for child goroutines
	var wg sync.WaitGroup
	for _, n := range numbers {
		wg.Add(1)
		go func foo() {
			fmt.Printf("%d ", n)
			wg.Done()
		}()
	}
	wg.Wait()
}
and check the result - we would expect to get the same thing as above, or the same numbers in different order, but instead we get

6 6 6 6 6 6

What’s wrong? We see that all goroutines see the same value of n, and the value they see is equal to the last value of this variable. This suggests that goroutines access the variable not when they are started, but at a later time, when the for loop has run through all elements of numbers.

This is in fact true - the anonymous function closes over the variable, and uses it’s value from the time it was executing, not from the time it was started. To fix the issue we can do two things - copy the loop variable to the for block:

for _, n := range numbers {
	wg.Add(1)
	var n = n
	go func foo() {
		fmt.Printf("%d ", n)
		wg.Done()
	}()
}
or binding the variable to a parameter of the anonymous function:
for _, n := range numbers {
	wg.Add(1)
	go func foo(n int) {
		fmt.Printf("%d ", n)
		wg.Done()
	}(n)
}
Both of those are correct, I prefer the second one but this is really a matter of taste. This problem is not specific to Go, and some other languages go to great lengths to help programmers avoid this trap - Microsoft introduced a backwards incompatible change in C# 5.0 to fix this.