liveBook · Manning

Range loop

As explained in 100 Go Mistakes and How to Avoid Them

A range loop is a Go loop construct that iterates over elements of an array, slice, map, string, pointer to an array, or receiving channel.

A range loop iterates over a string, array, pointer to an array, slice, map, or receiving channel without a manual index condition. For a slice, it supplies an index and an element value; use `_` to discard the index, or omit the value if only the index matters. A receiving channel supplies only a value. section 4.1.1

Mistake: changing the value variable and expecting the collection to change. In `for _, a := range accounts { a.balance += 1000 }`, each `a` is a copy. Balances of 100, 200, and 300 remain unchanged in the slice. To update the elements, write `for i := range accounts { accounts[i].balance += 1000 }`. A classic `for` loop is useful when you need finer control over which indexes to visit. A slice of pointers is another option when shared, mutable elements are intended. section 4.1.2

for i := range accounts {                #1
    accounts[i].balance += 1000
}
 
for i := 0; i < len(accounts); i++ {     #2
    accounts[i].balance += 1000
}

Both loops update the slice through its index rather than modifying a copied element value. section 4.1.2

The expression after `range` is evaluated once, before iteration. If a three-element slice is appended to inside its range loop, the loop still takes three iterations: it uses the original, three-length slice value. A classic loop that checks `len(s)` on every iteration behaves differently and can keep growing indefinitely. section 4.2

That one-time evaluation has different observable effects by type:

  • Channel: reassigning the channel variable inside the loop does not switch the channel being received from. section 4.2.1
  • Array: ranging over an array copies it. Changing the original array during iteration does not change later values read from that copy; range over an array pointer or read the original by index when those changes must be visible. section 4.2.2
  • Map: do not rely on key order, insertion order, deterministic iteration order, or seeing an entry added during iteration. section 4.7

A pointer to the wrong customer

In the illustrated `for _, customer := range customers` loop, storing `&customer` under each ID makes all three map entries point to the final customer. The address taken is that of the loop variable, not an element in the slice. Use `&customers[i]` when the map should point to the original elements; make a local copy inside the loop when it should point to separate copies. section 4.3

For a string, range supplies each rune and the starting byte index of its UTF-8 sequence. In `"hêllo"`, the indexes are 0, 1, 3, 4, and 5. Printing `s[i]` reads a byte, not the rune `ê`; use `for i, r := range s` and print `r`. Convert to `[]rune` if positions must count runes instead of bytes. section 5.2

How 100 Go Mistakes and How to Avoid Them covers it

How deep it goes
Works through observable results and contextual fixes for slices, arrays, channels, pointers, maps, and strings.
Examples
Traces unchanged account balances, a growing slice, reassigned channels, an edited array, customer pointers, and the string `"hêllo"`.
What it assumes
Assumes basic Go collection and loop syntax; refreshes range bindings and explains the distinction between UTF-8 bytes and runes.
What you can do after
Diagnose/debug with it when a loop changes copies, reads unexpected values, retains an original channel, or reports byte positions.
How it connects
Connects range loops to value copying, slice indexing, pointer semantics, channel iteration, map iteration, and UTF-8 runes.

Also explained in

Compare how 3 books explain range loop →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of 100 Go Mistakes and How to Avoid Them. The section links point to the parts of the book it draws on.