Iterators with the iter Package

Use Go iterators and the iter package for functional programming patterns and efficient data processing

The iter package provides foundational iterator interfaces and helper functions for functional programming patterns. Combined with new iterator support in slices and maps packages, it enables efficient and expressive data processing.

Basic Iterator Usage

Work with simple iterators using the iter.Seq interface:

package main

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

// Create a simple iterator that yields numbers.
func numbers(max int) iter.Seq[int] {
	return func(yield func(int) bool) {
		for i := 0; i < max; i++ {
			if !yield(i) {
				return
			}
		}
	}
}

func main() {
	// Use the iterator with range.
	fmt.Println("Numbers 0-4:")
	for num := range numbers(5) {
		fmt.Printf("%d ", num)
	}
	fmt.Println()
	
	// Collect iterator values into a slice.
	nums := slices.Collect(numbers(5))
	fmt.Printf("Collected: %v\n", nums)
}

Slice Iterator Functions

Use new iterator-based functions in the slices package:

package main

import (
	"fmt"
	"slices"
	"strconv"
)

func main() {
	data := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
	
	// Get all elements with indices as an iterator.
	fmt.Println("All elements with indices:")
	for i, val := range slices.All(data) {
		fmt.Printf("data[%d] = %d ", i, val)
	}
	fmt.Println()
	
	// Get values only.
	fmt.Println("Values only:")
	for val := range slices.Values(data) {
		fmt.Printf("%d ", val)
	}
	fmt.Println()
	
	// Get values backwards.
	fmt.Println("Backwards:")
	for val := range slices.Backward(data) {
		fmt.Printf("%d ", val)
	}
	fmt.Println()
	
	// Chunk into pairs and collect.
	chunked := slices.Collect(
		slices.Chunk(data, 2), // Chunk into pairs.
	)
	fmt.Printf("Chunked data: %v\n", chunked)
	
	// Convert to strings using an iterator
	strings := slices.Collect(func(yield func(string) bool) {
		for _, num := range data {
			if !yield(strconv.Itoa(num)) {
				return
			}
		}
	})
	fmt.Printf("String conversion: %v\n", strings)
}

Map Iterator Functions

Work with map iterators for efficient key-value processing:

package main

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

func main() {
	userAges := map[string]int{
		"Alice":   30,
		"Bob":     25,
		"Charlie": 35,
		"Diana":   28,
	}
	
	// Get all keys as a slice.
	names := slices.Collect(maps.Keys(userAges))
	slices.Sort(names) // Sort for consistent output.
	fmt.Printf("Names: %v\n", names)
	
	// Get all values as a slice.
	ages := slices.Collect(maps.Values(userAges))
	slices.Sort(ages)
	fmt.Printf("Ages: %v\n", ages)
	
	// Process key-value pairs.
	fmt.Println("User details:")
	for name, age := range maps.All(userAges) {
		fmt.Printf("  %s: %d years old\n", name, age)
	}
	
	// Filter and collect.
	adults := make(map[string]int)
	for name, age := range maps.All(userAges) {
		if age >= 30 {
			adults[name] = age
		}
	}
	fmt.Printf("Adults: %v\n", adults)
}

Key-Value Iterators

Work with iter.Seq2 for key-value iterations:

package main

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

// Create an iterator that yields index-value pairs.
func enumerate[T any](slice []T) iter.Seq2[int, T] {
	return func(yield func(int, T) bool) {
		for i, val := range slice {
			if !yield(i, val) {
				return
			}
		}
	}
}

// Filter key-value pairs.
func filterKV[K, V any](seq iter.Seq2[K, V], predicate func(K, V) bool) iter.Seq2[K, V] {
	return func(yield func(K, V) bool) {
		for k, v := range seq {
			if predicate(k, v) {
				if !yield(k, v) {
					return
				}
			}
		}
	}
}

func main() {
	fruits := []string{"apple", "banana", "cherry", "date", "elderberry"}
	
	// Enumerate with indices.
	fmt.Println("Enumerated fruits:")
	for i, fruit := range enumerate(fruits) {
		fmt.Printf("  %d: %s\n", i, fruit)
	}
	
	// Filter based on index and value.
	longFruits := filterKV(
		enumerate(fruits),
		func(i int, fruit string) bool {
			return len(fruit) > 5 // Fruits with more than 5 characters.
		},
	)
	
	fmt.Println("Long fruits:")
	for i, fruit := range longFruits {
		fmt.Printf("  %d: %s\n", i, fruit)
	}
}

Best Practices

  • Use iterators for lazy evaluation to process large datasets efficiently.
  • Combine iterator functions to build expressive data processing pipelines.
  • Prefer slices.Collect to materialize iterator results when needed.
  • Use iter.Seq2 for key-value iterations like enumeration or map processing.

Common Pitfalls

  • Forgetting that iterators are lazy; they don't execute until consumed.
  • Not checking the return value of yield in custom iterators, preventing early termination.
  • Creating infinite iterators without proper termination conditions.

Performance Tips

  • Iterators enable memory-efficient processing of large datasets.
  • Chain iterator operations to avoid intermediate slice allocations.
  • Use take and similar functions to limit processing when appropriate.
  • Profile iterator pipelines to ensure they provide the expected performance benefits.