Last Updated: December 31, 2025
The Iterator Design Pattern is a behavioral pattern that provides a standard way to access elements of a collection sequentially without exposing its internal structure.
At its core, the Iterator pattern is about separating the logic of how you move through a collection from the collection itself. Instead of letting clients directly access internal arrays, lists, or other data structures, the collection provides an iterator object that handles traversal.
It’s particularly useful in situations where:
When faced with this need, developers often write custom for loops or expose the underlying data structures (like ArrayList or LinkedList) directly. For example, a Playlist class might expose its songs array and let the client iterate however it wants.
But this approach makes the client tightly coupled to the collection’s internal structure, and it violates encapsulation. If the internal storage changes, the client code breaks. It also becomes difficult to add new traversal logic or support lazy iteration.
The Iterator pattern solves this by providing a uniform interface for traversal. Clients work with hasNext() and next() methods, completely unaware of whether the underlying structure is an array, a linked list, a tree, or something more exotic.
Let’s walk through a real-world example to see how we can apply the Iterator Pattern to build a more maintainable, extensible, and standardized approach to traversing collections.
Imagine you are building a music streaming application. Users can create playlists, add songs, and play them in various ways. A playlist might contain hundreds of songs, and the player needs to iterate through them one by one.
Your first implementation might look like this:
And your music player might use it like this:
This looks clean enough. The player gets the list of songs and iterates through them. What could go wrong?
As the application grows, several issues emerge:
By returning the internal list, you allow clients to do more than just read. They can add songs, remove songs, clear the list, or even replace it entirely. Nothing prevents a client from calling playlist.getSongs().clear() and wiping out the entire playlist.
Your player assumes the playlist uses a List<String>. What if you decide to change the internal structure? Perhaps you want to store songs in a database and load them lazily. Or maybe you want to use a Set to prevent duplicates.
Every change to the internal structure ripples through all client code.
What if you need to play songs in reverse order? Or shuffle them? Or skip songs that the user has marked as disliked?
Each of these requires writing new loop logic in the client. The playlist has no control over how its contents are accessed.
If your player directly accesses the list, testing the player in isolation becomes harder. You cannot easily mock or stub the playlist's behavior.
We need a way for clients to traverse the playlist that:
This is exactly what the Iterator Pattern provides.
The Iterator Pattern defines a separate object, the iterator, that encapsulates the details of traversing a collection. Instead of exposing its internal structure, the collection provides an iterator that clients use to access elements sequentially.
The pattern separates two concerns:
This separation means you can change how elements are stored without affecting how they are traversed, and vice versa.
Consider a TV remote control. When you press the "next channel" button, you do not need to know how the TV internally organizes its channel list. Maybe it is stored as an array, a linked list, or fetched from a satellite signal.
The remote provides a simple interface: next channel, previous channel. The complexity of channel management is hidden behind that interface.
The Iterator pattern works the same way. The iterator is like the remote control, providing a simple interface to move through a collection without exposing how that collection is structured internally.
The Iterator pattern involves four key components:
Declares the operations required to traverse a collection. At minimum, this includes hasNext() to check if more elements exist, and next() to retrieve the next element.
Implements the Iterator interface for a specific collection. It maintains the current position within the collection and knows how to move to the next element.
Declares a method for creating an iterator. This is often called createIterator() or iterator().
Implements the IterableCollection interface. It stores elements and returns an appropriate iterator when asked.
You might wonder why we need a separate iterator object. Why not just add hasNext() and next() methods directly to the collection?
The answer lies in supporting multiple simultaneous traversals. If the collection itself tracks the current position, you can only have one traversal at a time. But with separate iterator objects, you can have multiple iterators traversing the same collection independently.
This becomes important in multi-threaded applications or when you need to compare elements at different positions in the same collection.
Let us refactor our music playlist using the Iterator Pattern. We will build a complete implementation step by step.
Iterator InterfaceThis interface declares the standard operations for traversing any collection:
The interface is generic, allowing it to work with any element type. Two methods are sufficient for basic iteration:
hasNext() returns true if there are more elements to iteratenext() returns the current element and moves to the next positionSome iterator interfaces include additional methods like remove(), reset(), or current(). We are keeping it minimal here. You can always extend the interface based on your needs, but starting simple reduces complexity.
IterableCollection InterfaceThis interface ensures that any collection can provide an iterator:
Any class implementing this interface promises to provide an iterator for traversing its elements.
Now we implement the Playlist class. Notice that it no longer exposes its internal list:
The iterator maintains its position and knows how to traverse the playlist:
The client can now iterate through a playlist without knowing how it's implemented internally.
The client code is clean and focused. It does not know or care whether the playlist uses an ArrayList, LinkedList, or any other structure internally.
Let us evaluate what the Iterator Pattern has given us:
The internal list is no longer exposed. Clients cannot accidentally (or intentionally) modify the playlist's contents through the iterator. The playlist maintains full control over its data.
The client code works with the Iterator interface. If we later change the playlist to use a LinkedList, a database, or a streaming buffer, the client code remains unchanged. We only need to update the iterator implementation.
The Playlist class focuses on managing songs. The PlaylistIterator class focuses on traversal logic. Each class has one reason to change.
Each call to createIterator() returns a new, independent iterator. Multiple parts of your application can traverse the same playlist simultaneously without interfering with each other.
We can now easily add new types of iterators (reverse, shuffled, filtered) without modifying the Playlist class or existing client code.