Iterators give custom collections and lazy sequences the same for range syntax as slices and maps.
Go 1.23 lets for range loop over functions. An iterator is a function that takes a yield callback and calls it once per element:
func(yield func(V) bool) // iter.Seq[V]
func(yield func(K, V) bool) // iter.Seq2[K, V]The iter package names these types. When you write for v := range seq { ... }, the compiler turns the loop body into the yield function. yield returns false when the loop stops early (via break, return or a panic), and the iterator must stop calling it then — calling yield after it returned false panics at run time. The standard shape is:
if !yield(v) { return }These are push iterators: the sequence drives the loop. iter.Pull converts one into a pull-style next()/stop() pair when you need to step through two sequences in lockstep; always call stop when done.
The standard library embraces them:
slices.All,slices.Values,slices.Backward,slices.Collect,slices.Sorted.maps.Keys,maps.Values,maps.All,maps.Collect.- Go 1.24 added
strings.Lines,strings.SplitSeqand friends.
Iterators are lazy: nothing is computed until the loop asks, so infinite sequences and pipelines that stop early are cheap. Their natural partner is generics: functions like Filter[V any](seq iter.Seq[V], keep func(V) bool) iter.Seq[V] compose cleanly.
A method that returns an iterator is conventionally named All, or after what it yields (Keys, Lines). Most iterators can be ranged over more than once; document it if yours can't.
Example
package main
import (
"fmt"
"iter"
"maps"
"slices"
)
func Fib() iter.Seq[int] { // infinite, lazy
return func(yield func(int) bool) {
a, b := 0, 1
for {
if !yield(a) {
return // the loop stopped: so do we
}
a, b = b, a+b
}
}
}
func main() {
for n := range Fib() {
if n > 50 {
break // yield returns false
}
fmt.Print(n, " ") // 0 1 1 2 3 5 8 13 21 34
}
fmt.Println()
ages := map[string]int{"bo": 4, "al": 7}
fmt.Println(slices.Sorted(maps.Keys(ages))) // [al bo]
}Remember
iter.Seq[V]isfunc(yield func(V) bool); range over it directly- Stop as soon as yield returns false
- slices and maps provide All/Values/Keys/Collect/Sorted helpers
Watch out for
- Ignoring yield's return value — breaking out of the loop then causes a runtime panic.