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
| Role | Responsibility |
|---|---|
| Iterator | Defines an interface for accessing and traversing elements |
| ConcreteIterator | Implements the Iterator interface and tracks the current position in the traversal |
| Aggregate | Defines an interface for creating an Iterator object |
| ConcreteAggregate | Returns an instance of the appropriate ConcreteIterator |
TypeScript Example
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
Composes objects into tree structures to represent part-whole hierarchies. Composite lets clients treat individual objec…
Represents an operation to be performed on elements of an object structure. Visitor lets you define new operations witho…
Captures and externalizes an object's internal state so it can be restored later, without violating encapsulation. Memen…
Defines an interface for creating an object, but lets subclasses decide which class to instantiate. Factory Method lets …
More Behavioral Patterns
Explore All Design Patterns
Browse our complete reference of 23 design patterns with TypeScript examples, UML participants, and practical guidance.