Learn Go← Dashboard

if/else, switch, the lone for loop, break, continue.

Modern range (Go 1.22 & 1.23)

Two recent Go releases quietly reshaped the most common loop in the language. If you've read older Go books or blog posts, the advice in this lesson supersedes what they tell you.

Go 1.22: the loop variable is now per-iteration

For years, this was the #1 Go newbie trap:

// Pre-1.22 — DON'T copy-paste this advice
funcs := []func(){}
for _, v := range []int{1, 2, 3} {
    funcs = append(funcs, func() { fmt.Println(v) })
}
for _, f := range funcs { f() }
// Used to print 3, 3, 3 — every closure captured the SAME v.

Since Go 1.22, v is a fresh variable per iteration. The snippet above now prints 1, 2, 3. The infamous v := v shadow workaround is no longer needed.

Go 1.22: range over an integer

You can now range straight over an integer count, no for i := 0; i < n; i++ ritual:

go playground
Loading...

range 5 iterates i from 0 to 4. If you don't need the index, even shorter:

for range 3 {
    fmt.Println("knock")
}

Go 1.23: range over a function (iterators)

Go 1.23 lets you range over a function — that's the foundation of the new iter package. Library code increasingly returns iterator functions you walk directly:

import (
    "maps"
    "slices"
)

m := map[string]int{"a": 1, "b": 2}
for k, v := range maps.All(m) {     // maps.All returns an iter.Seq2
    fmt.Println(k, v)
}

evens := slices.Collect(filter(nums, func(n int) bool { return n%2 == 0 }))

You can also write your own:

func upTo(n int) iter.Seq[int] {
    return func(yield func(int) bool) {
        for i := 0; i < n; i++ {
            if !yield(i) { return }   // consumer broke out of the loop
        }
    }
}

for v := range upTo(5) { fmt.Println(v) }

iter.Seq[V] is "single value per step"; iter.Seq2[K, V] is "two values per step" (the shape maps.All returns).

What changed in your habits

  • Stop writing v := v inside range loops — it's now a no-op (and lint tools flag it).
  • Stop writing for i := 0; i < n; i++ when you just need a count — use for i := range n.
  • Read maps.All, slices.All, custom iter.Seq returns as ranges, not as channels or callbacks.
In Go 1.22+, this prints what? `for i, v := range []int{10, 20} { go func() { print(i, v) }() }`