The for ... range loop allows for easy iteration over elements of a slice, map, array, or string.
Example:
s := []int{1, 2, 3} for i, v := range s { fmt.Println(i, v) } m := map[string]int{"a":1, "b":2} for k, v := range m { fmt.Println(k, v) }
Key Nuance:
The variables i, v, k, etc. are reused across all iterations! This often becomes a source of bugs when passing them by reference inside the loop or launching goroutines inside range.
What happens if you start a goroutine inside range over a slice capturing the iteration variable? How can you avoid the error?
Answer: A typical error occurs: inside the goroutine, loop variables are used, which after the loop ends will have the last value. To avoid this — you need to create local copies:
nums := []int{1, 2, 3} for _, v := range nums { go func(val int) { fmt.Println(val) }(v) }
Story
In one project, range was used to fill a slice through several goroutines, forgetting to make copies of the loop variables. All goroutines printed the same value — the last one from the array, which severely spoiled the business logic.
Story
When ranging over a map, a reference to the value was saved in a new slice of pointers. As a result, all elements of the new slice referenced the same variable — the one that was used in the loop (a copy of the value). The bug manifested when these variables were updated outside the loop (panic: invalid memory address or unexpected data).
Story
In an internal tool, when iterating over a string with range, I started handlers for significant substrings, but for each iteration, I received offsets in bytes rather than Unicode characters. Result: incorrect handling for Unicode strings, incorrect character slicing.