AlgoMaster Logo

Iterator Design Pattern

Ashish

Ashish Pratap Singh

5 min read
Iterator

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.

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 abstracting the iteration logic into a dedicated object — the iterator. Collections provide an iterator via a method like createIterator(), and the client uses it to access elements one by one.

This makes it easy to change the collection’s structure, add new traversal styles, and preserve clean, encapsulated designs.

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're building a music streaming app that allows users to create and manage playlists. Each playlist stores a list of songs and provides features like:

  • Playing songs one by one
  • Skipping to the next or previous song
  • Shuffling songs
  • Displaying the current song queue

Let’s say you start with a simple implementation:

And your client code might look like this:

Why This Is a Problem

1. Breaks Encapsulation

By exposing the internal list of songs (getSongs()), you allow clients to directly modify the collection. This can lead to unintended side effects, like removing songs from the list, reordering them, or injecting nulls.

2. Tightly Couples Client to Collection Type

The client assumes that the playlist uses a List<String>. If you ever decide to change the internal storage (e.g., to a LinkedList, array, or even a streaming buffer), the client code breaks.

3. Limited Traversal Options

The client is stuck with the default iteration order. Supporting multiple traversal styles (e.g., reverse, shuffled, filtered) requires rewriting the loop logic every time — violating the Single Responsibility and Open/Closed principles.

4. Duplication and Rigidity

As more features are added (e.g., previewing songs, playing only favorites), the logic for traversing songs gets duplicated across multiple classes.

What We Really Need

We need a clean, standardized way to:

  • Traverse songs in a playlist without exposing the internal collection
  • Allow different traversal styles (forward, reverse, shuffle)
  • Abstract the iteration logic from the data structure itself
  • Preserve encapsulation and allow the playlist to change internally without affecting client code

This is exactly where the Iterator Pattern helps.

2. What is the Iterator Pattern

The Iterator Pattern provides a standard way to traverse elements in a collection without exposing its internal structure.

Instead of letting clients access the underlying data (like an ArrayList), the collection provides an iterator object that offers sequential access to its elements through a common interface (e.g., hasNext()next()).

Class Diagram

1. Iterator (interface)

Defines the contract for traversing a collection. Declares methods for traversing elements like:

  • hasNext()— checks if there are more elements
  • next()— returns the next element in the sequence

2. ConcreteIterator

  • Implements the Iterator interface for a specific collection.
  • Maintains the current position in the collection and iterates over it one item at a time.

3. IterableCollection (interface)

  • Defines a method for creating an iterator.
  • Separates the collection's structure from traversal logic

4. ConcreteCollection

  • Stores the actual data and implements the IterableCollection interface.
  • It delegates traversal logic to the iterator.
  • Returns iterator to traverse collection items

3. Implementing Iterator

Let’s now implement the Iterator Pattern to decouple the way we traverse songs in a playlist from how the playlist is internally structured.

We’ll build:

  • Playlist that stores songs
  • PlaylistIterator that can move through the playlist
  • A client that uses the iterator without needing to know how the playlist stores its songs

1. Define the Iterator Interface

This interface defines the standard operations needed to iterate over a collection.

2. Define the IterableCollection Interface

This represents any collection that can produce an iterator.

3. Implement the Concrete Collection – Playlist

This class will hold a list of songs and return an iterator to traverse them.

4. Implement the Concrete Iterator – PlaylistIterator

This class knows how to traverse a Playlist one song at a time.

5. Client Code – Using the Iterator

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

Output

What We Achieved

  • Encapsulation: The client never accesses the internal list directly
  • Consistent traversal interface: All iterators follow the same hasNext()/next() pattern
  • Open/Closed Principle: We can add new iterators (reverse, shuffled) without changing the playlist or player
  • Flexible architecture: Works with different collection types, not just lists
  • Reusable logic: The iterator can be used in any context that needs to traverse the playlist