Ranging Over Values
range walks over a list, a string, a set of pairs, a pipe or a count, handing you each item in turn.
for i, v := range collection { ... }Writing a counter by hand works, but it is easy to get the last index wrong. range does the walking for you. It goes through the values in something, one at a time, and gives you up to two values on each pass.
Three words you need here. A slice is a list of values that can grow. A map is a set of key-and-value pairs, looked up by key. A channel is a pipe that values are sent along; ranging over one hands you each value sent until it is closed. Each has its own lesson later.
What the two values are
| Ranging over | First value | Second value |
|---|---|---|
| slice or array | index, starting at 0 | a copy of the element |
| string | byte position | the character at it |
| map | key | value |
| integer n | 0 up to n-1 | none |
| channel | the value received | none |
Taking fewer values
for i, v := range s { } // both
for i := range s { } // first only
for range s { } // neither, just repeat
Ranging over an integer arrived in Go 1.22, and it is shorthand for a counted loop. Both of these run three times with i as 0, 1, 2:
for i := 0; i < 3; i++ { }
for i := range 3 { }
What trips people up
The second value is a copy. Changing it leaves the original untouched; assign to s[i] instead. Map order is deliberately unpredictable, so sort the keys before printing. Since Go 1.22 each pass gets its own fresh loop variables; in Go 1.21 and earlier one variable was reused for the whole loop, which surprised anyone who kept a reference to it.
Example
package main
import "fmt"
func main() {
names := []string{"ada", "alan", "grace"}
// Index and element.
for i, name := range names {
fmt.Println(i, name)
}
// Over a string you get the byte position and the character.
// "ñ" takes two bytes, so the next position jumps from 1 to 3.
for i, r := range "añb" {
fmt.Printf("%d %c\n", i, r)
}
// Over an integer: 0 up to n-1. Added in Go 1.22.
for i := range 3 {
fmt.Println("tick", i)
}
}
// Output:
// 0 ada
// 1 alan
// 2 grace
// 0 a
// 1 ñ
// 3 b
// tick 0
// tick 1
// tick 2When to use it
- Print every item in a list together with its position.
- Total the values in a map of counts.
- Walk a string character by character when it may contain accents or emoji.
- Repeat an action a fixed number of times without declaring a counter.
More examples
Printing a map in a fixed order
Go randomises map order on purpose, so any output or test that depends on it will fail eventually. Sorting the keys first is the fix. Use it for every line of output, log or response built from a map. Go 1.23 added a shorter spelling of the same three steps: slices.Sorted(maps.Keys(stock)).
package main
import (
"fmt"
"sort"
)
func main() {
stock := map[string]int{"pear": 4, "apple": 7, "fig": 1}
// Ranging a map directly gives the keys in an unpredictable order,
// so collect them, sort them, then range over the sorted list.
keys := make([]string, 0, len(stock))
for k := range stock {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
fmt.Printf("%s=%d\n", k, stock[k])
}
}
// Output:
// apple=7
// fig=1
// pear=4Changing elements in place
This is the single most common misunderstanding about range. Take the index form whenever the loop has to write back into the list, and the value form when it only reads.
package main
import "fmt"
func main() {
prices := []int{10, 20, 30}
// v is a copy, so this changes nothing outside the loop.
for _, v := range prices {
v = v * 2
}
fmt.Println("after copying:", prices)
// Assigning through the index does change the slice.
for i := range prices {
prices[i] = prices[i] * 2
}
fmt.Println("after indexing:", prices)
}
// Output:
// after copying: [10 20 30]
// after indexing: [20 40 60]Bytes versus characters in a string
Text is stored as bytes, and characters outside the plain English set take more than one byte each. A counted loop over len(s) walks bytes and will split such a character in half; range never does. Use range for anything that treats text as characters.
package main
import "fmt"
func main() {
s := "héllo"
// len counts bytes, and indexing a string gives one byte.
fmt.Println("bytes:", len(s))
// range decodes whole characters instead of bytes.
count := 0
for range s {
count++
}
fmt.Println("characters:", count)
for i, r := range s {
fmt.Printf("%d:%c ", i, r)
}
fmt.Println()
}
// Output:
// bytes: 6
// characters: 5
// 0:h 1:é 3:l 4:l 5:o
Discussion