Iterators

An iterator produces a sequence of values one at a time, so a for … range loop can walk through data without first building a slice of all of it. In Go, an iterator is simply a function that calls a yield callback for each value, and range accepts such functions directly. This lesson covers iter.Seq and iter.Seq2, writing your own iterators, the standard library's iterators, and composing them.

Ranging over a function

A function with the signature func(yield func(V) bool) can be used in a range loop. Each call to yield runs one pass of the loop body; yield returns false if the loop has stopped early (with break or return), and the iterator must then stop:

GO
package main

import "fmt"

func countdown(from int) func(yield func(int) bool) {
	return func(yield func(int) bool) {
		for i := from; i >= 0; i-- {
			if !yield(i) {
				return // the loop body said stop
			}
		}
	}
}

func main() {
	for n := range countdown(3) {
		fmt.Print(n, " ")
	}
	fmt.Println()

	for n := range countdown(100) {
		if n < 97 {
			break // yield returns false and the iterator returns
		}
		fmt.Print(n, " ")
	}
	fmt.Println()
}
Output
3 2 1 0
100 99 98 97

iter.Seq and iter.Seq2

The iter package names the two common shapes: iter.Seq[V] yields single values and iter.Seq2[K, V] yields pairs, like the index and value of a slice. Using these names makes signatures readable and lets your iterators work with library functions:

GO
package main

import (
	"fmt"
	"iter"
)

func Fibonacci() iter.Seq[int] { // infinite: the caller decides when to stop
	return func(yield func(int) bool) {
		a, b := 0, 1
		for {
			if !yield(a) {
				return
			}
			a, b = b, a+b
		}
	}
}

func Enumerate[T any](items []T) iter.Seq2[int, T] {
	return func(yield func(int, T) bool) {
		for i, v := range items {
			if !yield(i+1, v) { // numbered from 1
				return
			}
		}
	}
}

func main() {
	for f := range Fibonacci() {
		if f > 50 {
			break
		}
		fmt.Print(f, " ")
	}
	fmt.Println()

	for n, s := range Enumerate([]string{"wake up", "make coffee"}) {
		fmt.Printf("%d. %s\n", n, s)
	}
}
Output
0 1 1 2 3 5 8 13 21 34
1. wake up
2. make coffee

An infinite sequence is fine, because nothing is computed until the loop asks for the next value.

Iterators in the standard library

FunctionYields
slices.All(s), slices.Values(s), slices.Backward(s)Index/value pairs, values, pairs from the end
maps.Keys(m), maps.Values(m), maps.All(m)A map's keys, values, or pairs
strings.Lines, strings.SplitSeq, strings.FieldsSeqPieces of a string
bytes.Lines, bytes.SplitSeq, …The same for byte slices
slices.Chunk(s, n)Sub-slices of up to n elements

And these collect an iterator's values: slices.Collect, slices.Sorted, slices.AppendSeq, maps.Collect and maps.Insert:

GO
package main

import (
	"fmt"
	"maps"
	"slices"
	"strings"
)

func main() {
	prices := map[string]float64{"tea": 2.5, "cake": 3.75, "juice": 3}

	fmt.Println(slices.Sorted(maps.Keys(prices)))

	words := slices.Collect(strings.FieldsSeq("  iterators   are lazy  "))
	fmt.Println(words, len(words))

	index := maps.Collect(slices.All([]string{"zero", "one", "two"})) // map[int]string
	fmt.Println(index[2])
}
Output
[cake juice tea]
[iterators are lazy] 3
two

Building pipelines

Functions that take an iterator and return a new one can be chained into a pipeline. Each value flows through all the steps before the next value is produced, so no intermediate slices are built:

GO
package main

import (
	"fmt"
	"iter"
	"slices"
	"strings"
)

func Filter[V any](seq iter.Seq[V], keep func(V) bool) iter.Seq[V] {
	return func(yield func(V) bool) {
		for v := range seq {
			if keep(v) && !yield(v) {
				return
			}
		}
	}
}

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

func Take[V any](seq iter.Seq[V], n int) iter.Seq[V] {
	return func(yield func(V) bool) {
		if n <= 0 {
			return
		}
		count := 0
		for v := range seq {
			if !yield(v) {
				return
			}
			count++
			if count == n {
				return
			}
		}
	}
}

func main() {
	log := "INFO start\nERROR disk full\nINFO retry\nERROR timeout\nERROR disk full\n"

	lines := Map(strings.Lines(log), strings.TrimSpace)
	errs := Filter(lines, func(s string) bool { return strings.HasPrefix(s, "ERROR") })
	firstTwo := Take(errs, 2)

	fmt.Println(slices.Collect(firstTwo))
}
Output
[ERROR disk full ERROR timeout]

Because Take stops after two values, the rest of the log is never even split into lines. This laziness is what makes iterators efficient for large files and streams.

Iterators over your own types

A common pattern is an All method that lets callers range over a collection without seeing how it's stored:

GO
package main

import (
	"fmt"
	"iter"
)

type Tree struct {
	Left, Right *Tree
	Value       int
}

func (t *Tree) Insert(v int) *Tree {
	if t == nil {
		return &Tree{Value: v}
	}
	if v < t.Value {
		t.Left = t.Left.Insert(v)
	} else {
		t.Right = t.Right.Insert(v)
	}
	return t
}

// All yields the values in sorted order (an in-order walk).
func (t *Tree) All() iter.Seq[int] {
	return func(yield func(int) bool) {
		t.walk(yield)
	}
}

func (t *Tree) walk(yield func(int) bool) bool {
	if t == nil {
		return true
	}
	return t.Left.walk(yield) && yield(t.Value) && t.Right.walk(yield)
}

func main() {
	var root *Tree
	for _, v := range []int{50, 30, 70, 20, 40, 60} {
		root = root.Insert(v)
	}
	for v := range root.All() {
		fmt.Print(v, " ")
		if v >= 50 {
			break
		}
	}
	fmt.Println()
}
Output
20 30 40 50

Recursion with && makes the early stop propagate: once yield returns false, every level returns false without visiting anything else.

Pull iterators

A range loop pushes values into the loop body. Occasionally you need to pull values one at a time instead — for example, to merge two sequences. iter.Pull converts a sequence into a next function (and a stop function you must call when finished):

GO
package main

import (
	"fmt"
	"iter"
	"slices"
)

func main() {
	next, stop := iter.Pull(slices.Values([]string{"a", "b", "c"}))
	defer stop()

	first, ok := next()
	fmt.Println(first, ok)
	second, _ := next()
	fmt.Println(second)
}
Output
a true
b

Try it yourself

Write an iterator Range(start, end, step int) iter.Seq[int], then use it to print the multiples of 7 below 50.

Try it yourself · main.go
Edit the code, copy it into main.go in a new folder and run it with go run main.go (Go 1.27 or later). How to install Go
Output
0 7 14 21 28 35 42 49
Next: running code concurrently with goroutines.