AcademyDesign Patterns
Iterator pattern
Walking a collection without exposing how it is stored, the extra object between caller and data it costs, and the wrong answer when next() runs past the end or data mutates.
5 min readUpdated 3 exercises
Iterator is for walking a collection without the caller ever needing to know how that collection is actually stored underneath.
An Iterator presents a uniform way to step through a collection, hasNext() and next(), without the caller ever touching the underlying storage. Swap an array for a linked list or a database cursor behind it, and every loop written against the iterator keeps working unchanged.
The problem it solves
You have a list of radio stations stored in an array. Code elsewhere walks it with an index:
for (let i = 0; i < stations.length; i++) use(stations[i]);Then the data moves: a tree of categories, a linked list, a result set that arrives page by page from an API. Every loop that assumed an array breaks.
- Callers depend on the storage. They know it is indexed and has a
length. - Traversal logic is repeated. Every caller writes its own walk, and each one can get it wrong.
- Some collections have no index. A tree or a stream has no
stations[i].
The fix is to give the collection an object that knows how to walk it and exposes the same small interface, whatever the storage is.
Structure
Two parts:
- The iterator: keeps the current position and exposes
hasNext()andnext(). - The collection: creates iterators for itself, and can hide any internal structure.
class StationList {
constructor(stations) {
this.stations = stations;
this.index = 0;
}
hasNext() { return this.index < this.stations.length; }
next() {
const station = this.stations[this.index];
this.index += 1;
return station;
}
}
// const list = new StationList([98.5, 101.2, 104.9]);
// while (list.hasNext()) list.next(); → 98.5, 101.2, 104.9, in orderThe loop is the same whether the stations live in an array, a linked list or a database cursor. Only the iterator changes.
Where it is used in real systems
| Use case | What is being iterated |
|---|---|
| Arrays, Maps and Sets | Built into the language through for...of. |
| Generators | Values produced lazily, one at a time. |
| Paginated APIs | Fetch the next page only when the loop needs it. |
| Database cursors | Stream millions of rows without loading them all. |
| File and stream reading | Line by line, or chunk by chunk. |
| Tree traversal | Depth-first or breadth-first, without exposing the tree. |
| Infinite sequences | IDs, Fibonacci numbers, polling loops. |
Iterators you already use
JavaScript builds the pattern into the language.
- The iterator protocol. An object with
next()returning{ value, done }is an iterator. An object with a[Symbol.iterator]()method is iterable. - `for...of`, spread and destructuring all consume iterables:
[...set],const [a, b] = map. - Generators (
function*) build iterators withyield, without writing a class:
async function* allItems(fetchPage: (n: number) => Promise<string[]>) {
for (let page = 1; ; page++) {
const items = await fetchPage(page);
if (items.length === 0) return;
yield* items;
}
}
for await (const item of allItems(fetchPage)) use(item);- `for await...of` iterates asynchronous sources, such as streams and paginated fetches.
- Python uses
__iter__and__next__, and Java hasIterator<T>and the enhancedforloop.
Why lazy iteration matters
An iterator produces values on demand. That gives you two things an array cannot:
- Memory stays flat. Reading a 5 GB file line by line uses a few kilobytes, because only the current line exists.
- Work stops early.
findon a lazy source stops at the first match. An array would already have paid for everything.
This is the reason to reach for iterators and generators when data is large or comes over the network.
When not to use it
- A plain array is fine. For a small in-memory list,
for...ofover the array already is an iterator. Do not write your own. - You need random access. Iterators go forward. If you need the item at index 500, an array does that directly.
- The collection changes while you iterate. Adding or removing items during a walk gives surprising results, and behavior differs between collections. Copy first, or avoid mutating.
- Reuse. Most iterators are single-use. Once exhausted, they are done, and a second loop gets nothing. Make an iterable that creates a fresh iterator each time.
What interviewers ask
- "What is the Iterator pattern?" A uniform way to traverse a collection without exposing how it is stored.
- "How does JavaScript implement it?"
Symbol.iterator,next()returning{ value, done }, andfor...of. - "What is a generator?" A function that can pause with
yield, and produces an iterator automatically. - "Why is lazy iteration useful?" Constant memory and early exit on large or remote data.
- "Iterator versus iterable?" An iterable can produce an iterator. An iterator is the object with
next().
The mistake to avoid
An iterator that exposes or depends on the collection's internals, such as handing out the raw array or reading a length that can change mid-walk. The point is that the caller only sees hasNext() and next(). If callers can reach past the iterator to the storage, swapping the storage breaks them again.
The mechanism above, an object that remembers a position and hands out the next item, is the same in JavaScript, Python or Java. Only the syntax changes.
Now practice it
1 challenge · 2 quizzesReading this page is the cheap half. These are the exercises that make you use it.
- IteratorProImplement hasNext()/next() over a collection and prove a caller never touches the underlying storage — the decoupling this page walks through.ChallengeSolve · MediumChallengeSolve · Medium
- Design Patterns · Behavioral PatternsTen quick questions across Strategy, State, Observer, Command and the rest of the behavioral family, to check the mental model before this one.QuizTake · HardQuizTake · Hard
- Design Patterns · Architectural Patterns — Series 2ProA second, tougher pass on the same behavioral family, for once the first set feels easy.QuizTake · HardQuizTake · Hard
Read next
Visitor pattern
Adding a new operation to a fixed set of classes without editing them, the every-visitor update a new node type costs, and why it fails once classes change more than the ops.
6 min
Interpreter pattern
Evaluating a small tree of grammar rules one node at a time, the class per rule that costs fast past a toy language, and the point a real parser beats one more class.
6 min