Iterator pattern
The iterator pattern provides a uniform way to traverse any collection without exposing its internal structure. Decouple traversal logic from the data it walks over.
Introduction
The Iterator pattern provides a uniform way to traverse a collection without exposing how that collection stores its elements. The collection creates a traversal object, and the caller consumes that object through a small protocol.
TL;DR / mental model: Keep storage inside the aggregate and put traversal state in an iterator. The same client loop can then consume sequential, filtered, shuffled, tree, or cursor-backed data.
Problem and Context
You are building a music player. The playlist stores songs in an ArrayList. Callers loop through songs using index-based access, and everything works until someone decides the playlist should use a LinkedList for faster insertions. Every caller breaks because they assumed array indexing.
Three problems here. First, the caller knows the collection is an ArrayList and uses index access. Second, callers can mutate the internal list by adding or removing songs. Third, supporting multiple traversal orders (sequential, shuffled, genre-filtered) means duplicating loop logic everywhere.
When It Helps
Iterator helps when callers need a uniform traversal contract, the collection's representation should stay private, or several traversal modes need their own state. For a plain collection with one natural traversal, the language's built-in Iterable and for-each support may be enough.
Here is what changes when you apply the Iterator pattern.
Participants and Structure
Participants
- Iterator:
SongIterator, exposing traversal operations such ashasNext()andnext(). - Concrete iterators: sequential, shuffled, or genre-filtered cursors that hold traversal state.
- Aggregate:
Playlist, which owns the collection and creates the appropriate iterator. - Client: consumes the iterator without depending on the playlist's list implementation.
SongIterator defines the traversal contract: hasNext() and next(). Playlist implements IterablePlaylist and can produce multiple iterator types. Each concrete iterator knows how to walk the songs in a different order, but callers only see the SongIterator interface. The playlist never exposes its internal list.
Idiomatic Example and Implementation Notes
The key insight is that the collection creates the iterator but callers consume it through the interface. The pattern is useful when a collection needs more than one traversal order or when traversal state should not leak into callers.
Java's built-in support
Java's java.util.Iterator and Iterable interfaces implement the same traversal idea. Any class implementing Iterable works with the enhanced for-loop (for-each) automatically.
Continue Reading with Premium
Unlock this article and every other in-depth system design guide on the platform with SDEpedia Premium.
Related Articles
Learn how the composite pattern lets you treat individual objects and groups uniformly, building tree structures where clients never distinguish leaves from branches.
The factory method replaces direct constructor calls with a method that subclasses can override to return different types. Callers depend on an abstraction, not a concrete class.
The strategy pattern extracts a family of algorithms behind an interface so the client can swap behaviors at runtime without touching the context class.