Skip to content

Iterator

Iterator provides a way to access the elements of a collection one at a time, in order, without revealing the collection’s internal representation. The traversal logic — where we are, and how to advance — lives in a separate iterator object, so the same collection can be walked by different callers independently.

A collection needs to be looped over, but every collection stores its data differently: an array, a linked list, a tree, a ring buffer. If callers reach inside to walk the data, they become coupled to that storage, and changing the structure breaks every loop. Worse, two callers iterating at once would trip over each other if the position were kept on the collection itself.

The variation Iterator isolates is traversal. It moves the cursor — the current position and the rule for advancing — into its own object. Callers ask the collection for an iterator and then pull elements through a uniform interface, oblivious to the underlying layout. Because each iterator owns its position, several can traverse the same collection at the same time.

Most languages now offer native iteration protocols, so you rarely hand-roll the classic interface; you implement the language’s protocol instead and get for-loops, comprehensions, and lazy pipelines for free.

classDiagram
  class Iterable {
    <<interface>>
    +iterator() Iterator
  }
  class Iterator {
    <<interface>>
    +hasNext() bool
    +next() T
  }
  class NumberRing {
    -items: List~T~
    +iterator() Iterator
  }
  class RingIterator {
    -index: int
    +hasNext() bool
    +next() T
  }
  Iterable <|.. NumberRing
  Iterator <|.. RingIterator
  NumberRing ..> RingIterator : creates
A collection creates an Iterator that holds the cursor and advances independently
  • Iterator — the interface for stepping through elements: typically “is there another?” and “give me the next.”
  • Concrete Iterator — holds the current position and knows how to advance over one specific collection.
  • Iterable (Aggregate) — the collection; it creates iterators on request and hides its storage.
  • Client — asks for an iterator and consumes elements through the interface, unaware of the layout.

A small custom collection that yields its elements in order. Each language implements its native iteration protocol — Symbol.iterator and generators in TypeScript, __iter__ and generators in Python, a closure-based generator in Go, and the Iterator trait in Rust.

class NumberBox implements Iterable<number> {
private items: number[] = [];
add(n: number): void {
this.items.push(n);
}
// A generator is the idiomatic way to implement Symbol.iterator.
*[Symbol.iterator](): Iterator<number> {
for (const n of this.items) {
yield n;
}
}
}
const box = new NumberBox();
box.add(1);
box.add(2);
box.add(3);
for (const n of box) {
console.log(n); // 1, 2, 3
}
console.log([...box]); // [1, 2, 3] — spread uses the same protocol
  • Pro: callers traverse a collection without knowing or depending on its internal storage.
  • Pro: each iterator owns its position, so multiple traversals of one collection coexist.
  • Pro: it unifies looping across wildly different structures behind one interface, and pairs naturally with lazy, on-demand evaluation.
  • Con: for a plain array a direct index loop is simpler than a dedicated iterator object.
  • Con: mutating a collection while iterating it is a classic source of bugs, and most iterators do not tolerate it.
  • Composite trees are a natural home for iterators, which can flatten a recursive structure into a linear walk.
  • Template Method often drives traversal of a composite, with the iterator filling in the per-node step.
What does the Iterator pattern provide?
Why can several iterators traverse one collection at once?
Which is the idiomatic way to implement iteration in modern languages?
What is a common bug when using iterators?