100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Software Design

Iterator Pattern

A behavioral pattern that provides a way to access elements of an aggregate object sequentially without exposing its underlying representation.

Behavioral Patterns IIBeginner8 min readJul 10, 2026
Analogies

What Is the Iterator Pattern?

The Iterator pattern separates the traversal logic of a collection from the collection's own structure. Instead of exposing an internal array, linked list, or tree so client code can loop over it directly, the collection hands out an iterator object that knows how to walk through the elements one at a time, exposing a uniform interface such as hasNext() and next(). This means the collection is free to change its internal storage (say, from an array to a hash map) without breaking any client code that iterates over it.

🏏

Cricket analogy: A scorer walking through the overs of a Test match one delivery at a time doesn't need to know whether the raw data lives in a paper scorebook or a digital feed, just like Virat Kohli's innings can be replayed ball-by-ball regardless of how the broadcaster stores the footage internally.

Structure and Core Interfaces

The pattern typically involves two interfaces: Iterator, which declares next(), hasNext(), and sometimes current(), and Aggregate (or Iterable), which declares a createIterator() method that returns a fresh iterator instance. Concrete collections implement Aggregate and return a ConcreteIterator tailored to their internal structure — an array-backed collection might return an index-based iterator, while a tree-backed collection might return one that performs an in-order traversal using an internal stack. Because the iterator holds its own cursor state, multiple iterators over the same collection can traverse independently and concurrently.

🏏

Cricket analogy: Two commentators can independently rewind and replay a DRS review from different starting balls at the same time, each holding their own cursor into the same delivery-by-delivery data, just as two iterator instances traverse the same collection independently.

typescript
interface Iterator<T> {
  hasNext(): boolean;
  next(): T;
}

interface Aggregate<T> {
  createIterator(): Iterator<T>;
}

class NameCollection implements Aggregate<string> {
  private names: string[] = [];

  add(name: string): void {
    this.names.push(name);
  }

  createIterator(): Iterator<string> {
    return new NameIterator(this.names);
  }
}

class NameIterator implements Iterator<string> {
  private index = 0;
  constructor(private names: string[]) {}

  hasNext(): boolean {
    return this.index < this.names.length;
  }

  next(): string {
    return this.names[this.index++];
  }
}

const collection = new NameCollection();
collection.add('Ada');
collection.add('Grace');

const it = collection.createIterator();
while (it.hasNext()) {
  console.log(it.next());
}

Built-In Iterators in Modern Languages

Most modern languages bake the Iterator pattern directly into their syntax. JavaScript and TypeScript have the Symbol.iterator protocol, which lets any object work with for...of loops once it implements next() correctly; Python's __iter__ and __next__ dunder methods serve the same role, powering for x in collection; Java's Iterable/Iterator interfaces enable the enhanced for-loop. These language-level integrations mean developers rarely hand-write iterators for simple linear collections, but understanding the pattern is essential for building custom iterables, such as one that lazily generates Fibonacci numbers or streams rows from a database cursor without loading the entire result set into memory.

🏏

Cricket analogy: A ball-by-ball live commentary feed on ESPNcricinfo generates each delivery's data lazily as it happens, rather than pre-loading an entire match's data, much like a lazy Fibonacci iterator produces values on demand.

Java's Iterator also declares an optional remove() method, allowing safe removal of elements during traversal — using a collection's own remove() method mid-loop instead throws a ConcurrentModificationException.

External vs. Internal Iterators

External iterators, like the classic hasNext()/next() pair, put the client in control: the client explicitly calls next() to pull each element, which gives fine-grained control such as pausing traversal or interleaving it with other logic. Internal iterators, by contrast, invert control — the collection itself drives the traversal and pushes each element to a callback, as seen in JavaScript's Array.prototype.forEach or Java 8's Stream.forEach. Internal iterators are often more concise and can enable optimizations like parallel traversal internally, but they sacrifice the client's ability to break early or pause without extra mechanisms like exceptions or short-circuiting operators such as findFirst().

🏏

Cricket analogy: A batter choosing to face each ball at their own pace, calling for a drinks break mid-over, is like an external iterator; a bowling machine that fires balls automatically at fixed intervals is like an internal iterator pushing deliveries to the batter.

Mutating a collection (adding or removing elements) while an external iterator is mid-traversal is a classic source of bugs — many languages either throw a fail-fast exception or produce undefined behavior, so prefer collecting changes and applying them after iteration completes.

  • Iterator decouples traversal logic from a collection's internal representation.
  • The pattern defines an Iterator interface (hasNext/next) and an Aggregate interface (createIterator).
  • Multiple independent iterators can traverse the same collection concurrently, each with its own cursor.
  • Modern languages build the pattern into syntax: JavaScript's Symbol.iterator, Python's __iter__/__next__, Java's Iterable.
  • External iterators give the client pull-based control; internal iterators push elements via callbacks and can be more concise.
  • Custom iterators enable lazy evaluation, such as generating infinite sequences or streaming large datasets without loading everything into memory.
  • Mutating a collection during active external iteration is a common source of runtime errors.

Practice what you learned

Was this page helpful?

Topics covered

#SoftwareDesign#DesignPatternsStudyNotes#SoftwareEngineering#IteratorPattern#Iterator#Pattern#Structure#Core#Loops#StudyNotes#SkillVeris