Looping with range
A for … range loop walks through the items of a collection: the elements of a slice or array, the characters of a string, the entries of a map, the values arriving on a channel, or the numbers below an integer. This lesson covers each kind, what the two loop variables mean in each case, and two classic surprises — copied values and random map order.
Slices and arrays
Ranging over a slice gives the index and a copy of the element at that index. Use _ for the one you don't need, or leave the second variable out to get only indexes:
package main import "fmt" func main() { fruits := []string{"apple", "banana", "cherry"} for i, fruit := range fruits { fmt.Println(i, fruit) } for _, fruit := range fruits { fmt.Print(fruit, "; ") } fmt.Println() for i := range fruits { fmt.Print(i, " ") } fmt.Println() }
0 apple 1 banana 2 cherry apple; banana; cherry; 0 1 2
The value is a copy
The element variable holds a copy. Changing it doesn't change the slice. To modify elements, assign through the index:
package main import "fmt" func main() { prices := []float64{10, 20, 30} for _, p := range prices { p *= 2 // changes only the copy } fmt.Println(prices) for i := range prices { prices[i] *= 2 } fmt.Println(prices) }
[10 20 30] [20 40 60]
The first loop compiles and runs without complaint, which makes this an easy bug to miss. Remember the rule: to change elements, assign through the index.
Strings
Ranging over a string decodes UTF-8 and gives each character as a rune, together with the byte position where it starts. Characters outside plain ASCII take more than one byte, so the positions can jump:
package main import "fmt" func main() { for i, r := range "Añob" { fmt.Printf("byte %d: %c (%U)\n", i, r, r) } }
byte 0: A (U+0041) byte 1: ñ (U+00F1) byte 3: o (U+006F) byte 4: b (U+0062)
Indexing a string directly, as in s[1], gives a single byte instead. Lesson 19 explains the difference.
Maps
Ranging over a map gives each key and its value. The order is deliberately unpredictable and can change from one run to the next, so never rely on it. When you need a stable order, sort the keys first:
package main import ( "fmt" "maps" "slices" ) func main() { stock := map[string]int{"pears": 4, "apples": 12, "figs": 0, "kiwis": 7} total := 0 for _, qty := range stock { // order doesn't matter for a sum total += qty } fmt.Println("total:", total) for _, fruit := range slices.Sorted(maps.Keys(stock)) { fmt.Printf("%-7s %2d\n", fruit, stock[fruit]) } }
total: 23 apples 12 figs 0 kiwis 7 pears 4
maps.Keys produces the keys and slices.Sorted collects them into a sorted slice. Lessons 27 and 28 cover these packages.
Integers
As you saw in the previous lesson, range n counts from 0 to n−1:
package main import "fmt" func main() { for i := range 3 { fmt.Print(i) } fmt.Println() }
012
Channels and functions
range also works on channels, receiving values until the channel is closed (lesson 52), and on iterator functions, which produce values one at a time (lesson 50). Here's a quick preview of both:
package main import ( "fmt" "strings" ) func main() { ch := make(chan string, 3) ch <- "a" ch <- "b" close(ch) for v := range ch { fmt.Print(v, " ") } fmt.Println() for word := range strings.FieldsSeq("one two three") { fmt.Print("[", word, "]") } fmt.Println() }
a b [one][two][three]
Summary
| Ranging over | First variable | Second variable |
|---|---|---|
| Slice or array | index (int) | copy of the element |
| String | byte position (int) | character (rune) |
| Map | key | value |
| Channel | received value | — |
| Integer n | 0 to n−1 | — |
| Iterator function | whatever it yields | whatever it yields (for two-value iterators) |
Try it yourself
Count how often each letter appears in a sentence, ignoring spaces, and print the counts in alphabetical order.
a:4 b:2 d:1 e:1 n:2 r:1
break, continue and labels.