Design Patterns · Behavioral Patterns
Iterator
Expose traversal without exposing a collection's representation. Give each traversal its own progress and define mutation behavior.
This lesson follows Command. It also builds on the tree in Composite.
Iterator moves traversal logic and progress into a separate object. A client asks for the next element without knowing storage details. The cost is iterator state and a contract for changes during traversal.
Hide traversal details, not ordering
A menu tree has root A, children B and C, and grandchild D under B. A search screen wants each node. Depth-first traversal yields A, B, D, C. Breadth-first traversal yields A, B, C, D. Both visit the same nodes. Their order differs.
Try it
Traverse the same tree two ways
A → B → D → C
A → B → C → D
type TreeNode = { name: string; children: TreeNode[] };
function* depthFirst(root: TreeNode): Generator<TreeNode> {
const stack = [root];
while (stack.length > 0) {
const current = stack.pop()!;
yield current;
for (let i = current.children.length - 1; i >= 0; i--) {
stack.push(current.children[i]!);
}
}
}
const tree: TreeNode = {
name: "A", children: [
{ name: "B", children: [{ name: "D", children: [] }] },
{ name: "C", children: [] },
],
};
const first = depthFirst(tree);
const second = depthFirst(tree);
first.next().value?.name; // A
first.next().value?.name; // B
second.next().value?.name; // A
for (const node of depthFirst(tree)) console.log(node.name);
A JavaScript generator provides the iterator protocol. Calling the generator function creates a fresh iterator. Each owns its own stack. A collection wrapper can return these through Symbol.iterator or named traversal methods.
Keep progress per iterator
Step through
Pause one traversal and start another
First iterator yields A
first → A
first pending: C, B
Children are pushed in reverse so B is popped first.
First iterator yields B
first → B
first will push D when resumed
The generator pauses at yield. Its local variables remain available for the next call.
Second iterator starts independently
second → A
first remains paused at B
A cursor stored on the collection itself would make the two clients interfere.
Iteration does not freeze the collection
The example reads children as traversal advances and assumes an acyclic tree. Changing children midway can change what is visited. Choose a snapshot, forbid mutation, detect a version change, or document live behavior. Graph traversal also needs a visited set when cycles or repeated nodes are possible.
Use the language's traversal contract first
Most modern languages build the pattern in. JavaScript has the iterator protocol, Symbol.iterator, and generators. Java has Iterator and Iterable. Python has __iter__ and __next__. Use built-in iterators for ordinary arrays. A separate abstraction earns its cost when storage is complex, traversal is reused, or data arrives incrementally.
Check yourself
An ordinary array already supports for-of. Must it gain a custom iterator hierarchy?
Not quite. The language's built-in iterator already supplies the contract.
Correct. A new abstraction needs a pressure that the built-in iterator does not address.
Map the roles
| Role | In this example | Job |
|---|---|---|
| Iterator | the protocol: next() returning { value, done } | Declares the operations needed to traverse a collection: fetch the next element, and sometimes get the current position or restart |
| Concrete iterators | the depthFirst generator with its private stack | Implement one traversal algorithm and track their own progress, so several iterators can walk one collection independently |
| Collection | a wrapper that exposes [Symbol.iterator]() or depthFirst() | Declares methods that return iterators. Their return type is the iterator interface, so concrete collections can return different kinds of iterators. |
| Concrete collections | the menu tree | Return a new concrete iterator each time a client asks |
| Client | a for...of loop | Works with collections and iterators through their interfaces, so the same code handles many collections and traversals. Usually gets iterators from the collection rather than creating them. |
Check yourself
Why does the collection return a new iterator on every request instead of keeping one cursor?
Correct. Independent iterators are what make parallel and paused traversals possible.
Not quite. Iterators do not freeze a collection. Mutation rules must be defined separately.
Reach for it when traversal should not leak structure
- your collection has a complex data structure that you want to hide from clients, for convenience or for safety. Clients get a few simple methods, and they cannot corrupt the collection by poking at its internals.
- you want to cut duplicated traversal code across your app. Non-trivial traversal code is often bulky. Inside business logic, it blurs the original code's responsibility.
- your code must traverse different data structures, or structures whose types are unknown in advance. Code that depends only on the collection and iterator interfaces still works when you pass it new kinds of collections and iterators.
Check yourself
Three screens each contain the same 30-line breadth-first walk over the org chart. Which Iterator use applies?
Correct. Move the walk into one iterator and let every screen consume it.
Not quite. Safety may also improve, but the pressure here is duplication.
Implement it in five steps
Refactor existing code toward the pattern in this order:
- Declare the iterator interface. At minimum it fetches the next element. For convenience, add methods such as fetching the previous element, tracking the current position, or checking for the end.
- Declare the collection interface with a method for fetching iterators. Its return type must be the iterator interface. Declare several such methods if you plan several groups of iterators.
- Implement concrete iterator classes for the collections you want to traverse. Link each iterator object to one collection instance, usually through the iterator's constructor.
- Implement the collection interface in your collection classes. Their methods give clients a shortcut to the right iterator, and the collection passes itself to the iterator's constructor.
- Replace the traversal code in the client with iterators. The client fetches a new iterator every time it needs to walk the collection.
Check yourself
In step 3, how is an iterator linked to the collection it walks?
Not quite. Nothing global is needed. Each iterator holds its own collection reference.
Correct. The collection passes itself in when it creates the iterator.
Name what it costs
| You gain | You pay |
|---|---|
| Bulky traversal algorithms move into their own classes (single responsibility) | Overkill if your app only works with simple collections |
| New collections and iterators work with existing code (open/closed) | Going through an iterator can be slower than walking some specialized collections directly |
| The same collection can be traversed in parallel, because each iterator has its own state | Ordering, mutation, and cycle behavior still need documentation |
| A traversal can pause and resume when needed | Every iterator stores its own progress |
Check yourself
A hot loop sums a plain numeric array a million times per second. Should it go through a custom iterator object?
Not quite. They add a layer, which can cost time in tight loops.
Correct. Iterator buys abstraction. A simple array often does not need it.
Do not confuse it with its neighbors
| Pattern | How it relates |
|---|---|
| Composite | Use iterators to traverse composite trees. |
| Factory Method | Collection subclasses can use a factory method to return the iterator type that matches them. |
| Memento | Combine them to capture the current iteration state and roll back to it if needed. |
| Visitor | Combine them to traverse a complex structure and run an operation on its elements, even when the elements belong to different classes. Iterator supplies the traversal, and Visitor supplies the operation. |
Check yourself
A search must remember exactly where it was in a tree so the user can return to that point later. Which pattern pairs with Iterator for this?
Not quite. A single instance does not save or restore position.
Correct. A memento captures the iteration position without exposing the iterator's internals.
Retrieve and apply
Check yourself
Two callers traverse one collection. Where should each caller's current position live?
Correct. Independent traversal requires independent progress. The collection can remain shared.
Not quite. Advancing one caller would also advance the other's traversal.
Predict the first four elements from depth-first and breadth-first traversal before selecting each option. Continue to Mediator to centralize cooperation among components.
Source: Alexander Shvets, Dive Into Design Patterns (深入设计模式), Chinese edition v2021-1.25. Iterator, printed pages 272–286. Explanations, examples, and exercises are adapted for this course.