Go cookbook · Advanced
Stream results with range-over-func iterators
Paginated APIs or database rows are loaded fully into memory before processing.
Approach
Return an iter.Seq2[T, error] that yields items page by page; callers use a plain for range loop and can stop
early with break, which stops fetching further pages.
Solution
package main
import (
"context"
"fmt"
"iter"
)
type Customer struct{ ID int }
// fetchPage simulates an API that returns pages of customers and a next cursor.
func fetchPage(ctx context.Context, cursor int) ([]Customer, int, error) {
if cursor >= 9 {
return nil, -1, nil
}
page := []Customer{{cursor + 1}, {cursor + 2}, {cursor + 3}}
return page, cursor + 3, ctx.Err()
}
func AllCustomers(ctx context.Context) iter.Seq2[Customer, error] {
return func(yield func(Customer, error) bool) {
cursor := 0
for cursor >= 0 {
page, next, err := fetchPage(ctx, cursor)
if err != nil {
yield(Customer{}, err)
return
}
for _, c := range page {
if !yield(c, nil) {
return // caller stopped early: don't fetch more pages
}
}
cursor = next
}
}
}
func main() {
for c, err := range AllCustomers(context.Background()) {
if err != nil {
fmt.Println("error:", err)
break
}
if c.ID > 4 {
break
}
fmt.Println("customer", c.ID)
}
}
Pitfalls
- Ignoring the result of
yield; continuing after it returns false panics. - Hiding errors in iterators that only yield values; use
iter.Seq2[T, error]. - Holding resources (rows, connections) without releasing them when the caller breaks; use
deferinside the iterator function.
Variations
slices.Collect(seq)materialises aniter.Seqwhen you do need a slice.- Wrap
*sql.Rowsin an iterator that closes the rows when iteration ends.
Further reading: iter package
Reference: https://pkg.go.dev/iter
More Go recipes
- Benchmark code with testing.B.Loop
- Build a small, secure container image for a Go service
- Decode and validate JSON request bodies safely
- Embed static files and templates in the binary
- Load and validate configuration from environment variables
- Log structured JSON with slog and redact secrets
- Process jobs concurrently with a bounded worker pool
- Profile a running service with pprof