Introduction
The range keyword iterates over elements in a variety of built-in data structures. It provides a clean, idiomatic way to loop over slices, arrays, maps, and strings, automatically handling the iteration details.
The Code
// _range_ iterates over elements in a variety of
// built-in data structures. Let's see how to
// use `range` with some of the data structures
// we've already learned.
package main
import "fmt"
func main() {
// Here we use `range` to sum the numbers in a slice.
// Arrays work like this too.
nums := []int{2, 3, 4}
sum := 0
for _, num := range nums {
sum += num
}
fmt.Println("sum:", sum)
// `range` on arrays and slices provides both the
// index and value for each entry. Above we didn't
// need the index, so we ignored it with the
// blank identifier `_`. Sometimes we actually want
// the indexes though.
for i, num := range nums {
if num == 3 {
fmt.Println("index:", i)
}
}
// `range` on map iterates over key/value pairs.
kvs := map[string]string{"a": "apple", "b": "banana"}
for k, v := range kvs {
fmt.Printf("%s -> %s\n", k, v)
}
// `range` can also iterate over just the keys of a map.
for k := range kvs {
fmt.Println("key:", k)
}
// `range` on strings iterates over Unicode code
// points. The first value is the starting byte index
// of the `rune` and the second the `rune` itself.
// See [Strings and Runes](strings-and-runes) for more
// details.
for i, c := range "go" {
fmt.Println(i, c)
}
}
Range with Different Types
Range Over Slices and Arrays
nums := []int{2, 3, 4}
sum := 0
for _, num := range nums {
sum += num
}
When ranging over slices or arrays, you get two values per iteration:
- Index - the position in the slice/array
- Value - the element at that position
Use the blank identifier _ to ignore values you don’t need.
The blank identifier _ is used to ignore return values you don’t need. This is idiomatic Go and more explicit than creating an unused variable.
Getting Index and Value
for i, num := range nums {
if num == 3 {
fmt.Println("index:", i)
}
}
When you need both the index and value, capture both variables in the for loop.
Range Over Maps
kvs := map[string]string{"a": "apple", "b": "banana"}
for k, v := range kvs {
fmt.Printf("%s -> %s\n", k, v)
}
Ranging over a map yields key/value pairs. The first variable is the key, the second is the value.
Map iteration order is not guaranteed and may vary between runs. Don’t rely on maps being iterated in any particular order.
Range Over Map Keys Only
for k := range kvs {
fmt.Println("key:", k)
}
If you only need the keys, use a single variable. The values are not computed or returned.
Range Over Strings
for i, c := range "go" {
fmt.Println(i, c)
}
// Output:
// 0 103 (g)
// 1 111 (o)
Ranging over a string iterates over Unicode code points (runes), not bytes:
- First value (
i): Starting byte index of the rune
- Second value (
c): The rune itself as an int32
For multi-byte UTF-8 characters, the byte index may not increment by 1 each iteration. For example, ‘世’ takes 3 bytes, so indices might be 0, 3, 6, etc.
Range Patterns
| Pattern | Syntax | Use Case |
|---|
| Value only | for _, v := range slice | Process each element |
| Index and value | for i, v := range slice | Need position info |
| Index only | for i := range slice | Just need positions |
| Key and value | for k, v := range map | Process map entries |
| Key only | for k := range map | Just need keys |
| Rune iteration | for i, c := range str | Process Unicode characters |
Range Return Values Summary
| Data Structure | First Value | Second Value |
|---|
| Slice/Array | Index (int) | Element value |
| Map | Key | Value |
| String | Byte index (int) | Rune (int32) |
| Channel | Element value | (none) |
Common Range Patterns
Filtering a Slice
var evens []int
for _, num := range nums {
if num%2 == 0 {
evens = append(evens, num)
}
}
Finding in a Slice
found := false
for _, item := range items {
if item == target {
found = true
break
}
}
doubled := make([]int, len(nums))
for i, num := range nums {
doubled[i] = num * 2
}
Collecting Map Keys
keys := make([]string, 0, len(m))
for k := range m {
keys = append(keys, k)
}
Important Notes
The value returned by range is a copy of the element, not a reference to it. Modifying the loop variable won’t change the original collection.
// This does NOT modify the slice
for _, v := range nums {
v = v * 2 // Only modifies the copy
}
// This DOES modify the slice
for i := range nums {
nums[i] = nums[i] * 2
}
Range vs Traditional For Loop
Use Range When:
- You want to iterate over all elements
- You need cleaner, more readable code
- You’re working with built-in types (slices, maps, strings)
Use Traditional For When:
- You need more control over iteration (step size, direction)
- You want to modify the original collection
- You need to iterate a specific number of times
Key Takeaways
range provides a clean way to iterate over collections
- Returns different values based on the data structure type
- Use
_ to ignore values you don’t need
- Works with slices, arrays, maps, strings, and channels
- Map iteration order is not guaranteed
- String iteration processes Unicode runes, not bytes
- Range values are copies - use indices to modify originals
- For slices/arrays: returns index and value
- For maps: returns key and value
- For strings: returns byte index and rune