Iterator Pattern: Traverse Without Exposing Storage

Iterator provides a standard way to traverse a collection while hiding whether the underlying structure is an array, tree, stream, or remote page.

What is the Iterator pattern?

The Iterator pattern is a behavioral design pattern that provides a standard way to access the elements of a collection sequentially without exposing how the collection stores them. The consumer sees only "is there a next item?" and "give me the next item" — whether the elements live in an array, a tree, a paginated API, or a file being streamed from disk.

This is the most thoroughly absorbed pattern in modern languages: JavaScript's for...of, Symbol.iterator, and generators are the pattern as first-class syntax. What remains worth learning is the design move — when you hand callers an iterator instead of your internal array, you keep the freedom to change storage, add laziness, or stream from the network without breaking a single consumer.

The logic, step by step

Problem

Consumers depend on collection internals and must be rewritten whenever storage or traversal order changes.

Rule

Expose traversal state and next-item behavior through an iterator contract rather than revealing collection representation.

Run
  1. COLLECTION creates an iterator for the requested traversal policy.
  2. CONSUMER asks whether another item exists and requests it when ready.
  3. ITERATOR advances internal position without exposing storage details.
Output

Consumers process items consistently while collections remain free to change representation or traversal strategy.

Iterator pattern example in TypeScript

TypeScript
// A paginated API exposed as a plain async iterable —
// consumers cannot tell it is not an in-memory array.
class OrderArchive {
  constructor(private api: OrdersApi) {}

  async *[Symbol.asyncIterator](): AsyncIterator<Order> {
    let cursor: string | undefined;
    do {
      const page = await this.api.listOrders({ cursor, limit: 100 });
      yield* page.orders;              // hand items out one by one
      cursor = page.nextCursor;        // pagination stays private
    } while (cursor);
  }
}

const archive = new OrderArchive(api);

// The consumer traverses thousands of remote orders like a local list:
for await (const order of archive) {
  if (order.total > 10_000) flagForReview(order);
}
The generator hides cursors, page sizes, and network calls. Swap the implementation to read from a local cache tomorrow and the for-await loop does not change.

Run it in the real world

A feed renderer consumes the same iterator interface for an in-memory list, paginated API results, or a lazily streamed archive.

When to use it — and when not to

Reach for Iterator when

  • Consumers should traverse a structure without depending on whether it is an array, tree, stream, or remote pages.
  • You want lazy traversal — produce items on demand instead of materializing everything in memory.
  • One collection needs multiple traversal orders (depth-first and breadth-first) behind one consuming loop.

Skip it when

  • The language primitive already fits — exposing a plain array and letting for...of work is not a design failure.
  • The collection mutates during traversal; iterators over changing data need explicit invalidation rules or snapshots.

Command pattern — Both reify something implicit into an object: Command turns an action into data, Iterator turns traversal position into data. That reification is what lets infrastructure pause, resume, and compose them.

Facade pattern — An iterator is a narrow facade over traversal: one tiny interface hiding cursors, recursion stacks, or network pagination underneath.

Frequently asked questions

What does traversing mean in a data structure?

Traversing means visiting the elements of a structure one by one in some defined order — front to back for a list, depth-first or breadth-first for a tree. The Iterator pattern packages a traversal into an object so consumers can walk the structure without knowing its layout.

Are JavaScript generators the Iterator pattern?

Yes — a generator function is a factory for iterator objects, with the language maintaining the traversal state between yields. function* and Symbol.iterator are the GoF pattern promoted into syntax.

What is the difference between internal and external iterators?

With an external iterator the consumer pulls items and controls pacing (for...of, calling next()). With an internal iterator the collection pushes items into a callback (forEach). External iterators compose better — you can pause, interleave two of them, or stop early without exceptions.