GGistDev

Range in Go

for range iterates over built-in collections and (Go 1.23+) custom iterators.

Range over built-in types

range yields an index/key and a value. For strings, the value is a rune and the index is a byte offset.

Slice and array

xs := []int{10, 20, 30}
for i, v := range xs { _ = i; _ = v }

arr := [3]string{"a","b","c"}
for i, v := range arr { _ = i; _ = v }

Details:

  • On slices, the header (ptr/len/cap) is evaluated once. The value v is a copy of each element.
  • On arrays, ranging over the array value copies the entire array. Prefer ranging over &arr or arr[:] to avoid copying large arrays.
// Avoid copying: range over pointer to array or slice view
for i, v := range &arr { _ = i; _ = v }
for i, v := range arr[:] { _ = i; _ = v }

Map (unordered)

m := map[string]int{"a":1, "b":2}
for k, v := range m { _ = k; _ = v }

If order matters, gather keys and sort them before iterating.

keys := make([]string, 0, len(m))
for k := range m { keys = append(keys, k) }
slices.Sort(keys) // Go 1.21+, import "slices"
for _, k := range keys { v := m[k]; _ = v }

Notes:

  • Deleting during iteration is safe; newly added keys may or may not be visited.
  • Concurrent writes to a map are not safe and can panic; guard with a mutex or use sync.Map.

String (runes)

for i, r := range "héllo" {
    _ = i  // byte index
    _ = r  // rune (Unicode code point)
}

If you need manual control, use utf8.DecodeRuneInString to step through bytes safely.

Channel

ch := make(chan int)
go func(){ defer close(ch); for i:=0;i<3;i++ { ch<-i } }()
for v := range ch { _ = v }

Details:

  • Ranging a channel receives until the channel is closed, then the loop exits.
  • For senders, always close the channel when done so receivers can terminate.

Tips:

  • Map iteration order is not guaranteed; do not rely on it
  • Range over strings yields runes, not bytes; use []byte(s) for bytes
  • If you mutate a slice while ranging, prefer ranging over indices
  • The value in for i, v := range xs is a copy; taking &v points to the loop variable
  • Ignore index or value using _; index-only: for i := range xs { ... }, value-only: for _, v := range xs { ... }
  • When launching goroutines inside a loop, capture the loop variables:
for i, v := range xs {
    i, v := i, v // shadow
    go func(){ _ = i; _ = v }()
}

Range over iterators (Go 1.23+)

Go’s iterator primitives (iter.Seq, iter.Seq2) let you implement custom iteration with for range.

Single-value iterator

package main

import (
    "fmt"
    "iter"
)

// Seq that yields squares up to n-1
func Squares(n int) iter.Seq[int] {
    return func(yield func(int) bool) {
        for i := 0; i < n; i++ {
            if !yield(i*i) { return }
        }
    }
}

func main() {
    for v := range Squares(5) { fmt.Println(v) }
}

Key-value iterator

func Enumerate[T any](xs []T) iter.Seq2[int, T] {
    return func(yield func(int, T) bool) {
        for i, v := range xs { if !yield(i, v) { return } }
    }
}

Composition (map / filter)

func Map[T any, R any](in iter.Seq[T], f func(T) R) iter.Seq[R] {
    return func(yield func(R) bool) {
        for v := range in {
            if !yield(f(v)) { return }
        }
    }
}

func Filter[T any](in iter.Seq[T], p func(T) bool) iter.Seq[T] {
    return func(yield func(T) bool) {
        for v := range in {
            if p(v) { if !yield(v) { return } }
        }
    }
}

Bridge to channel

func ToChan[T any](s iter.Seq[T], buf int) <-chan T {
    ch := make(chan T, buf)
    go func(){
        defer close(ch)
        for v := range s { ch <- v }
    }()
    return ch
}

Iterator tips:

  • Always check yield return and stop early if it returns false
  • Keep iterators lazy; avoid precomputing entire results
  • Use Seq2 for index/value or key/value patterns
  • Favor composition for readability; keep each iterator focused on a single concern

Evaluation semantics (important)

  • The range expression is evaluated once at loop entry (slice header, map header, string value, channel reference).
  • The iteration variables (i, v) are reused each iteration; they get new values on each pass.
  • For arrays: ranging over a value copies it; prefer a slice or pointer-to-array for large arrays.

Summary

  • range works uniformly over slices, arrays, maps, strings, channels
  • Go 1.23+ iterators (iter.Seq, iter.Seq2) enable custom, composable iteration