Iterators and range-over-func Deep Dive

Updated

September 8, 2026

Iterators and range-over-func Deep Dive

Overview

Go 1.22–1.23 made for range over functions a language feature, with package iter defining Seq / Seq2. This is a deep dive on semantics and costs — not just syntax.

Also: slices/maps/cmp/iter, loops.

Diagram: Push iterator

flow:
  [Yield] --true--> [Next]
  [Yield] --false--> [Stop]

Types

type Seq[V any]     func(yield func(V) bool)
type Seq2[K, V any] func(yield func(K, V) bool)

The iterator pushes values into yield. Returning false from yield stops early (break).

Desugaring Intuition

for v := range All(s) {
    if v < 0 {
        break
    }
}
// roughly calls All(s)(func(v T) bool { ...; return continue? })

Panic safety: iterator code should treat yield like a callback that may not return normally if the consumer panics — stdlib iterators are careful; custom ones should be too.

Pull Iterators

next, stop := iter.Pull(seq)
defer stop()
for {
    v, ok := next()
    if !ok {
        break
    }
}

Pull converts push-style to pull-style via a small coroutine-like parking protocol — convenient adapters, slight overhead.

When Iterators Shine

  • Lazy pipelines over large or infinite streams
  • Library APIs that used to allocate intermediate slices
  • Single implementation for range and slices.Collect

When They Don’t

  • Tiny hot loops where a classic index for is clearer and fully BCE-friendly
  • Need for random access
  • Over-abstracting one-off loops

Experiment

go mod init example
go run .
package main

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

func Filter[V any](s []V, ok func(V) bool) iter.Seq[V] {
    return func(yield func(V) bool) {
        for _, v := range s {
            if !ok(v) {
                continue
            }
            if !yield(v) {
                return
            }
        }
    }
}

func main() {
    nums := []int{1, 2, 3, 4, 5}
    for v := range Filter(nums, func(n int) bool { return n%2 == 0 }) {
        fmt.Println("even", v)
    }
    fmt.Println("collect", slices.Collect(Filter(nums, func(n int) bool { return n > 3 })))
}

What to notice: No intermediate filtered slice until Collect.

Try next: Implement Map and chain Map(Filter(...)); compare allocs to nested slice loops.