[go: up one dir, main page]

AlgoMaster Logo

Iterator Design Pattern

Last Updated: December 31, 2025

Ashish

Ashish Pratap Singh

5 min read

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:

  • You need to traverse a collection (like a list, tree, or graph) in a consistent and flexible way.
  • You want to support multiple ways to iterate (e.g., forward, backward, filtering, or skipping elements).
  • You want to decouple traversal logic from collection structure, so the client doesn't depend on the internal representation.

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.

1. The Problem: Traversing a Playlist

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?

Why This Becomes a Problem

As the application grows, several issues emerge:

1. Breaks Encapsulation

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.

2. Tightly Couples Client to Implementation

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.

3. Limited Traversal Options

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.

4. Testing becomes difficult

If your player directly accesses the list, testing the player in isolation becomes harder. You cannot easily mock or stub the playlist's behavior.

What We Really Need

We need a way for clients to traverse the playlist that:

  • Does not expose the internal data structure
  • Provides a consistent interface regardless of how songs are stored
  • Allows the playlist to control how iteration happens
  • Supports different traversal strategies without modifying client code

This is exactly what the Iterator Pattern provides.

2. Understanding the Iterator Pattern

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 Core Idea

The pattern separates two concerns:

  1. The collection knows how to store elements
  2. The iterator knows how to traverse those elements

This separation means you can change how elements are stored without affecting how they are traversed, and vice versa.

The Structure

The Iterator pattern involves four key components:

1. Iterator (interface)

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.

2. ConcreteIterator

 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.

3. IterableCollection (interface)

Declares a method for creating an iterator. This is often called createIterator() or iterator().

4. ConcreteCollection

Implements the IterableCollection interface. It stores elements and returns an appropriate iterator when asked.

3. Implementing the Iterator Pattern

Let us refactor our music playlist using the Iterator Pattern. We will build a complete implementation step by step.

Step 1: Define the Iterator Interface

This 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 iterate
  • next() returns the current element and moves to the next position

Step 2: Define the IterableCollection Interface

This interface ensures that any collection can provide an iterator:

Any class implementing this interface promises to provide an iterator for traversing its elements.

Step 3: Implement the Concrete Collection

Now we implement the Playlist class. Notice that it no longer exposes its internal list:

Step 4: Implement the Concrete Iterator

The iterator maintains its position and knows how to traverse the playlist:

Step 5: Using the Iterator (Client Code)

The client can now iterate through a playlist without knowing how it's implemented internally.

Output

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.

4. What We Gained

Let us evaluate what the Iterator Pattern has given us:

Encapsulation is preserved

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.

Implementation independence

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.

Single Responsibility Principle

The Playlist class focuses on managing songs. The PlaylistIterator class focuses on traversal logic. Each class has one reason to change.

Multiple simultaneous traversals

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.

Foundation for extensions

We can now easily add new types of iterators (reverse, shuffled, filtered) without modifying the Playlist class or existing client code.