Behavioral Complexity: Low

Iterator in Go

Traverse a collection without exposing its internal representation, keeping traversal state separate from the aggregate.

The Problem

Collections are often easy to traverse when they are just slices, but traversal logic gets messy when you need custom ordering, filtering, or resumable state. Embedding those rules into every caller duplicates behavior and ties clients to the collection internals.

The Solution

Iterator moves traversal state into a dedicated object. In Go, a simple iterator interface with Next or HasNext plus a concrete iterator struct is usually enough. The aggregate exposes a factory method for creating the iterator, and callers consume the collection through that traversal contract.

Structure

  • Iterator interface: ProductIterator controls traversal.
  • Concrete iterator: SliceIterator tracks position over the catalog.
  • Aggregate: Catalog creates iterators for its internal collection.
  • Client: Consumes items through the iterator without reaching into aggregate internals.

Implementation

This example iterates over a product catalog using an explicit iterator object. The caller sees a stable traversal contract even though the aggregate owns the underlying slice and traversal state.

package main

type Product struct {
	SKU     string
	InStock bool
}

type ProductIterator interface {
	HasNext() bool
	Next() Product
}

Best Practices

  • Keep iterator interfaces tiny so callers only depend on traversal behavior.
  • Use iterators when traversal rules matter; plain for range stays clearer for simple slices.
  • Let the aggregate create iterators so collection internals stay encapsulated.
  • Document whether iterators are snapshot-based or observe live collection changes.
  • Avoid forcing iterator objects into code that naturally reads as ordinary range loops.

When to Use

  • Traversal needs custom state, ordering, or filtering.
  • You want to hide the collection representation from callers.
  • The same collection may need several traversal strategies.

When NOT to Use

  • A plain slice and for range loop are already obvious.
  • The iterator abstraction would be more ceremony than benefit.
  • Traversal is trivial and there is no internal representation worth hiding.