range is the form of for that walks a collection. It gives you one or two values per iteration, and what those values are depends on the type you range over:
| Operand | First value | Second value | Notes |
|---|---|---|---|
slice []T, array [n]T | index int | element T, a copy | |
pointer to array *[n]T | index | element | |
| string | byte index int | rune | decodes UTF-8 |
map map[K]V | key K | value V | random order |
channel chan T | element T | none | until the channel is closed |
integer n | 0 to n minus 1 | none | Go 1.22 |
func(yield func() bool) | none | none | Go 1.23 |
func(yield func(V) bool) | V | none | Go 1.23 |
func(yield func(K, V) bool) | K | V | Go 1.23 |
Choosing Which Variables You Get
for i, v := range s {} // both
for i := range s {} // index (or key) only
for _, v := range s {} // value only
for range s {} // neither: just repeat len(s) times
Declaring a variable you do not use is a compile error, so drop or blank the ones you do not need. for range 3 {} is the shortest way to repeat something three times.
The Value Is a Copy
On each iteration, the element is copied into the value variable. Changing the variable does not change the collection:
Copying also costs time when the elements are large structs. For a slice of big structs, for i := range s with s[i] avoids the copy.
Strings Yield Runes
Ranging over a string decodes UTF-8. The first value is the byte offset where each character starts, so it can skip numbers:
To walk bytes instead of characters, use an index loop, for i := 0; i < len(s); i++, or range over []byte(s). See runes and bytes for the UTF-8 details.
Maps: Random Order
Map iteration order is unspecified, and the runtime randomizes it so that code cannot come to depend on one order. For deterministic output, sort the keys:
Deleting entries while ranging is safe, and a deleted entry that has not been reached is not produced. Adding entries is allowed but unpredictable: a new key may or may not show up later in the same loop. fmt.Println of a map prints keys sorted, which is why the last line is stable.
Channels: Until Closed
Ranging over a channel receives values until the channel is closed and drained. If nobody closes it, the loop blocks forever (and if every goroutine is blocked, the runtime stops with fatal error: all goroutines are asleep - deadlock!):
The sender closes the channel, never the receiver. See channels for buffered channels and select.
Integers (Go 1.22)
range n counts from 0 up to, but not including, n. The type of the loop variable is the type of n:
for i := range 3 {} // i is int: 0, 1, 2
for i := range uint8(3) {} // i is uint8
for range 0 {} // runs zero times
A negative n also runs zero times. n is evaluated once, before the loop starts.
What range Evaluates, and When
The expression after range is evaluated once, before the first iteration. For a slice, range then uses that slice header, so its length is fixed:
The appended 99 is never visited because the loop length was fixed at 3. But nums[2] = 30 is seen, because at that point nums still shares its backing array with the slice being ranged over. Swap the two lines and the 30 disappears too: the append exceeds the capacity of 3, allocates a new array, and the write lands there instead. Ranging over an array value copies the whole array first, so the change to arr[2] is not seen; range over &arr or arr[:] to avoid the copy.
Range over Functions (Go 1.23)
Since Go 1.23, range also accepts iterator functions. An iterator takes a yield callback, calls it once per value, and stops when yield returns false (which happens when the loop body executes break or return):
iter.Seq[V] and iter.Seq2[K, V] are the standard names for the two iterator shapes. The standard library returns them from slices.All, slices.Values, slices.Backward, maps.Keys, maps.Values, maps.All, and in Go 1.24 from strings.SplitSeq, strings.Lines and bytes.SplitSeq. The iterator must respect yield's return value: calling yield again after it returned false panics.
Loop Variables Are Per Iteration (Go 1.22)
Since Go 1.22, the variables declared by for ... range are new on every iteration. Capturing v in a closure or goroutine captures that iteration's value, not a shared variable that ends up holding the last element. Before Go 1.22 you needed v := v inside the loop; that line is now redundant. The rule is set by the go version in go.mod, so an old module keeps the old behavior.
Common Mistakes
- Taking the address of the value variable. Before Go 1.22,
ptrs = append(ptrs, &v)stored the same address every time. Since 1.22 it stores distinct addresses, but each points at a copy, not at the slice element. Use&items[i]to point into the slice. - Expecting map order. Output that looks sorted in a small test will not stay sorted.
- Ranging over a channel nobody closes. The loop, and the goroutine running it, waits forever.
- Ranging over a large array by value. The whole array is copied first. Range over a slice of it instead.
Frequently Asked Questions
What does range return in Go?
It depends on the operand. For a slice or array: index and element. For a string: byte index and rune. For a map: key and value. For a channel: each received value. For an integer n (Go 1.22): 0 to n minus 1. For an iterator function (Go 1.23): whatever the function yields. You can omit the second variable, or discard the first with _.
Why doesn't modifying the range value change my slice?
The value variable is a copy of the element. for _, v := range items { v.Price = 0 } changes the copy only. Use the index instead: for i := range items { items[i].Price = 0 }, or range over a slice of pointers.
Can I range over an integer in Go?
Yes, since Go 1.22: for i := range 5 { ... } runs with i from 0 to 4. for range 5 { ... } repeats five times without a variable. The module's go.mod must say go 1.22 or later.
Is it safe to delete from a map while ranging over it?
Yes. Deleting an entry that has not been reached yet means it will not be produced, and deleting the current entry is fine. Adding entries during the loop is also allowed, but a new entry may or may not be visited, so do not rely on it.