Low-level design2 min
Iterator Pattern
Type: Behavioral Pattern
Relevance: Extremely High. It is literally built into almost every modern programming language (e.g., IEnumerable in C#, Iterable in Java, Symbol.iterator in JavaScript).
The Iterator pattern lets you traverse elements of a collection without exposing its underlying representation (list, stack, tree, etc.).
Real-Life Analogy
Think of a TV Remote Control.
A TV has a complex underlying data structure mapping channels to frequencies. But as a user, you don't care how it's stored. You just press the Next Channel button. The remote is the Iterator. It knows how to traverse the channels without you having to understand the TV's internal circuitry.
The Problem
Collections can be stored in many ways: Arrays, Linked Lists, Trees, Hash Maps.
If your client code wants to loop through a collection, it shouldn't have to know how that collection is structured. If you change your data structure from an Array to a Tree, you don't want to rewrite all your for loops.
How to Implement Iterator
- Define an
Iteratorinterface with methods likegetNext()andhasMore(). - Define an
IterableCollectioninterface with a methodcreateIterator(). - The concrete collection implements
createIterator()to return an instance of a specific Iterator class that knows how to traverse that specific collection.
Example in Code
// 1. The Iterator Interface
interface Iterator<T> {
current(): T;
next(): T;
key(): number;
valid(): boolean;
rewind(): void;
}
// 2. The Collection Interface
interface Aggregator<T> {
getIterator(): Iterator<T>;
}
// 3. Concrete Collection and Iterator
class WordsCollection implements Aggregator<string> {
private items: string[] = [];
public getItems(): string[] { return this.items; }
public getCount(): number { return this.items.length; }
public addItem(item: string): void { this.items.push(item); }
public getIterator(): Iterator<string> {
return new AlphabeticalOrderIterator(this);
}
}
class AlphabeticalOrderIterator implements Iterator<string> {
private collection: WordsCollection;
private position: number = 0;
constructor(collection: WordsCollection) {
this.collection = collection;
}
public rewind(): void { this.position = 0; }
public current(): string { return this.collection.getItems()[this.position]; }
public key(): number { return this.position; }
public next(): string {
const item = this.collection.getItems()[this.position];
this.position += 1;
return item;
}
public valid(): boolean {
return this.position < this.collection.getCount();
}
}
// Client Code
const collection = new WordsCollection();
collection.addItem("First");
collection.addItem("Second");
collection.addItem("Third");
const iterator = collection.getIterator();
// The client loops through without knowing it's an array under the hood!
while (iterator.valid()) {
console.log(iterator.next());
}
Class Diagram
Note on Modern Languages
In JavaScript/TypeScript, you rarely implement this from scratch anymore. You use generators (function*) or the built-in [Symbol.iterator] protocol to make an object compatible with standard for...of loops!