Skip to content
SP StackPractices
beginner

Iterator Pattern

Provide a way to access elements of a collection sequentially without exposing its underlying representation. A behavioral design pattern for traversal.

Topics: design

Iterator Pattern

Overview

The Iterator Pattern is a behavioral design pattern that provides a way to access elements of an aggregate object sequentially without exposing its underlying representation. It separates the traversal logic from the collection itself, allowing multiple simultaneous traversals and different traversal strategies.

When to Use

Use the Iterator Pattern when:

  • You need to traverse a collection without exposing its internal structure
  • You want to support multiple traversal algorithms (forward, backward, filter)
  • You need to allow traversal by multiple clients simultaneously
  • You want a uniform interface for traversing different collection types
  • The collection’s internal representation may change

Solution

Python

class BookCollection:
    def __init__(self):
        self._books = []

    def add(self, book: str):
        self._books.append(book)

    def __iter__(self):
        return iter(self._books)

    def reverse_iter(self):
        return reversed(self._books)

# Usage
collection = BookCollection()
collection.add("Design Patterns")
collection.add("Clean Code")
collection.add("Refactoring")

# Forward iteration (built-in iterator protocol)
for book in collection:
    print(book)

# Reverse iteration
for book in collection.reverse_iter():
    print(book)

JavaScript

class BookCollection {
  constructor() {
    this.books = [];
  }

  add(book) {
    this.books.push(book);
  }

  *[Symbol.iterator]() {
    for (const book of this.books) {
      yield book;
    }
  }

  *reverseIterator() {
    for (let i = this.books.length - 1; i >= 0; i--) {
      yield this.books[i];
    }
  }
}

// Usage
const collection = new BookCollection();
collection.add("Design Patterns");
collection.add("Clean Code");
collection.add("Refactoring");

// Forward
for (const book of collection) {
  console.log(book);
}

// Reverse
for (const book of collection.reverseIterator()) {
  console.log(book);
}

Java

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class BookCollection implements Iterable<String> {
    private final List<String> books = new ArrayList<>();

    public void add(String book) {
        books.add(book);
    }

    @Override
    public Iterator<String> iterator() {
        return books.iterator();
    }

    public Iterator<String> reverseIterator() {
        return new ReverseIterator<>(books);
    }
}

class ReverseIterator<T> implements Iterator<T> {
    private final List<T> list;
    private int index;

    public ReverseIterator(List<T> list) {
        this.list = list;
        this.index = list.size() - 1;
    }

    @Override
    public boolean hasNext() {
        return index >= 0;
    }

    @Override
    public T next() {
        return list.get(index--);
    }
}

// Usage
BookCollection collection = new BookCollection();
collection.add("Design Patterns");
collection.add("Clean Code");
collection.add("Refactoring");

for (String book : collection) {
    System.out.println(book);
}

Explanation

The Iterator Pattern has two roles:

  • Aggregate (BookCollection): The collection that holds elements
  • Iterator: Provides sequential access to elements without exposing the collection’s internals

Modern languages integrate iterators deeply — Python’s __iter__, JavaScript’s Symbol.iterator, and Java’s Iterable/Iterator interfaces are all examples of this pattern.

Variants

VariantDescriptionUse Case
External IteratorClient controls traversal (next(), hasNext())Flexible, explicit control
Internal IteratorCollection applies a function to each element (forEach)Simpler client code
Reverse IteratorTraverses in reverse orderStacks, undo history
Filtering IteratorSkips elements that don’t match a predicateSearch, filtering

Best Practices

  • Use language-native iterator protocols when available instead of custom classes
  • Throw exceptions on invalid next() calls to fail fast
  • Support remove() only when semantically valid and document clearly
  • Make iterators fail-fast if the collection is modified during iteration
  • Document whether iteration order is guaranteed or arbitrary

Common Mistakes

  • Exposing the internal collection directly instead of using an iterator
  • Not handling concurrent modification during iteration, leading to undefined behavior
  • Implementing complex iterator classes when a simple generator or comprehension suffices
  • Creating iterators that don’t implement the language’s native iterator protocol
  • Forgetting to reset iterator state, causing unexpected behavior on reuse

Frequently Asked Questions

Q: Do I need to implement the Iterator Pattern manually? A: Rarely. Most languages provide built-in iterator support. Only implement a custom iterator when you need a non-standard traversal (e.g., tree traversal, graph traversal, or filtered iteration).

Q: What is the difference between Iterator and Visitor? A: Iterator traverses elements. Visitor performs operations on elements. They are often used together: an iterator walks the structure, and a visitor processes each element.