🔄 Behavioral

Iterator Pattern

Provides a way to access the elements of an aggregate object sequentially without exposing its underlying representation. Iterator encapsulates the traversal logic, allowing different traversal strategies for the same collection.

Problem

Collections have different underlying data structures—arrays, trees, graphs, hash tables. But you want to traverse them with a uniform interface. Exposing the internal structure of a collection to client code couples clients to specific implementations and makes it impossible to change the collection's structure without breaking clients.

Solution

Extract the traversal behavior into a separate iterator object. The iterator implements a standard interface (typically next, hasNext, or Symbol.iterator in TypeScript). The collection provides a method to create an appropriate iterator. Multiple iterators can traverse the same collection independently and simultaneously.

Participants

RoleResponsibility
IteratorDefines an interface for accessing and traversing elements
ConcreteIteratorImplements the Iterator interface and tracks the current position in the traversal
AggregateDefines an interface for creating an Iterator object
ConcreteAggregateReturns an instance of the appropriate ConcreteIterator

TypeScript Example

TypeScript
class RangeIterator implements Iterable<number> {
  constructor(
    private start: number,
    private end: number,
    private step: number = 1,
  ) {}

  [Symbol.iterator](): Iterator<number> {
    let current = this.start;
    const end = this.end;
    const step = this.step;

    return {
      next(): IteratorResult<number> {
        if (current <= end) {
          const value = current;
          current += step;
          return { value, done: false };
        }
        return { value: undefined, done: true };
      },
    };
  }
}

class TreeNode<T> {
  children: TreeNode<T>[] = [];
  constructor(public value: T) {}

  *depthFirst(): Generator<T> {
    yield this.value;
    for (const child of this.children) {
      yield* child.depthFirst();
    }
  }

  *breadthFirst(): Generator<T> {
    const queue: TreeNode<T>[] = [this];
    while (queue.length > 0) {
      const node = queue.shift()!;
      yield node.value;
      queue.push(...node.children);
    }
  }
}

for (const n of new RangeIterator(1, 5)) {
  console.log(n); // 1, 2, 3, 4, 5
}

const root = new TreeNode("A");
root.children.push(new TreeNode("B"), new TreeNode("C"));
root.children[0].children.push(new TreeNode("D"));

console.log([...root.depthFirst()]);   // [A, B, D, C]
console.log([...root.breadthFirst()]); // [A, B, C, D]

Real-World Example

JavaScript's iteration protocol (Symbol.iterator, for...of loops, spread operator) is the Iterator pattern built into the language. Database cursors iterate over query results without loading the entire result set into memory. File system directory traversals, social media feed pagination, and streaming APIs all implement iterators. Generators in JavaScript and Python are syntactic sugar for creating custom iterators.

When to Use

  • You need to access a collection's elements without exposing its internal structure
  • You want to support multiple types of traversal over a collection
  • You want a uniform interface for traversing different types of collections
  • You need to provide multiple simultaneous traversals of the same collection

When Not to Use

  • The collection is simple and a standard for loop is sufficient
  • You only need one traversal strategy and will never need another
  • The overhead of creating iterator objects outweighs the abstraction benefit (performance-critical hot paths)

Understanding the Iterator Pattern

The Iterator pattern is one of the most important behavioral design patterns in object-oriented software design. Originally cataloged by the Gang of Four (Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides) in their seminal 1994 book "Design Patterns: Elements of Reusable Object-Oriented Software," this pattern continues to be a cornerstone of modern software architecture.

Provides a way to access the elements of an aggregate object sequentially without exposing its underlying representation. Iterator encapsulates the traversal logic, allowing different traversal strategies for the same collection. The pattern involves the following key participants: Iterator, ConcreteIterator, Aggregate, ConcreteAggregate. Each participant has a well-defined role, and the interactions between them create a flexible, maintainable structure that can evolve with changing requirements.

In TypeScript and modern JavaScript development, the Iterator pattern is particularly valuable because the language's type system and interface support allow you to express the pattern with compile-time safety. TypeScript generics can make Iterator implementations even more flexible and reusable while maintaining strong type checking throughout the pattern's structure.

Knowing when to apply the Iterator pattern is as important as knowing how. Common use cases include: You need to access a collection's elements without exposing its internal structure; You want to support multiple types of traversal over a collection; You want a uniform interface for traversing different types of collections. However, overusing design patterns can lead to unnecessarily complex code. Always evaluate whether the pattern genuinely solves your problem or if a simpler approach would suffice.

The Iterator pattern is related to several other design patterns: Composite, Visitor, Memento, Factory Method. Understanding these relationships helps you choose the right pattern for your specific situation and combine patterns effectively when building complex systems.

Related Patterns

More Behavioral Patterns

Explore All Design Patterns

Browse our complete reference of 23 design patterns with TypeScript examples, UML participants, and practical guidance.