3 min read

Iterator Pattern

The Iterator Pattern is a behavioral design pattern that lets you traverse elements of a collection without exposing its underlying representation (list, stack, tree, etc.).

1. The Core Concept

Collections are one of the most used data types in programming. However, storing data is only half the job; you also need to retrieve it.

  • The Problem: How do you traverse a complex data structure (like a Graph or Tree) sequentially? Depth-first? Breadth-first? Random?
  • The Solution: Extract the traversal behavior of a collection into a separate object called an Iterator.

The Iterator object encapsulates all the traversal details, such as the current position and how many elements are left.

2. Analogy: Touring a City

You plan to visit Rome.

  • The Collection: The city of Rome and its sights.
  • Iterator 1: A physical map (You walk randomly).
  • Iterator 2: A Google Maps walking route (Optimized for distance).
  • Iterator 3: A local tour guide (Optimized for history).

The city remains the same, but the way you traverse it changes depending on the "Iterator" you choose.

3. Example (TypeScript)

Most modern languages (JS, Python, Java) have built-in iterators (e.g., for..of loops). Here is how it works under the hood.

// The Iterator Interface
interface Iterator<T> {
    current(): T;
    next(): T;
    key(): number;
    valid(): boolean;
    rewind(): void;
}

// The Aggregator Interface
interface Aggregator {
    getIterator(): Iterator<string>;
}

// Concrete Iterator
class AlphabeticalOrderIterator implements Iterator<string> {
    private collection: WordsCollection;
    private position: number = 0;
    private reverse: boolean = false;

    constructor(collection: WordsCollection, reverse: boolean = false) {
        this.collection = collection;
        this.reverse = reverse;

        if (reverse) {
            this.position = collection.getCount() - 1;
        }
    }

    public rewind() {
        this.position = this.reverse ?
            this.collection.getCount() - 1 :
            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 += this.reverse ? -1 : 1;
        return item;
    }

    public valid(): boolean {
        if (this.reverse) {
            return this.position >= 0;
        }
        return this.position < this.collection.getCount();
    }
}

// Concrete Collection
class WordsCollection implements Aggregator {
    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);
    }

    public getReverseIterator(): Iterator<string> {
        return new AlphabeticalOrderIterator(this, true);
    }
}

// Usage
const collection = new WordsCollection();
collection.addItem("First");
collection.addItem("Second");
collection.addItem("Third");

const iterator = collection.getIterator();

console.log("Straight traversal:");
while (iterator.valid()) {
    console.log(iterator.next());
}
// Output: First, Second, Third

console.log("\nReverse traversal:");
const reverseIterator = collection.getReverseIterator();
while (reverseIterator.valid()) {
    console.log(reverseIterator.next());
}
// Output: Third, Second, First

4. Pros and Cons

Pros Cons
SRP: Extracts bulky traversal algorithms into separate classes. Overkill: If your app only works with simple lists, using an iterator is overkill compared to a simple for loop.
OCP: You can implement new types of collections and iterators without breaking existing code. Efficiency: Using a specialized iterator might be less efficient than direct traversal for some specialized collections.
Parallel Iteration: You can iterate over the same collection in parallel because each iterator object contains its own iteration state.

5. Generators (Yield)

Many modern languages (Python, JavaScript, C#) support Generators, which provide a syntactic shortcut for creating iterators. Instead of building a class with next() and maintaining state manually, you write a function that uses the yield keyword.

  • Yield: When yield is called, the function pauses execution and returns the value. When next() is called again, the function resumes exactly where it left off.

Example (JavaScript/TypeScript)

function* numberGenerator() {
    yield 1;
    yield 2;
    yield 3;
}

const gen = numberGenerator(); // Returns an Iterator
console.log(gen.next().value); // 1
console.log(gen.next().value); // 2
console.log(gen.next().value); // 3

programming/design-patterns programming/object-oriented-programming