Design Patterns Iterator

The Iterator pattern provides a standard way to move through a collection of items without exposing how that collection is stored internally. Whether the items sit in an array, a linked list, a tree, or a database result set, you access them the same way: call next(), check hasNext(), and process each item.

Think of a TV remote. You press the next-channel button without knowing how the channel list is arranged in memory. The remote gives you one channel at a time. That is an iterator.

What Problem Does It Solve?

Suppose you have a playlist stored as an array in one class and a watch-later list stored as a linked list in another. Without Iterator, every piece of code that processes these lists must know whether to use array indexing or pointer traversal:

// Processing an array playlist
for (int i = 0; i < playlist.length; i++) {
    play(playlist[i]);
}

// Processing a linked list — completely different code
Node current = watchLater.head;
while (current != null) {
    play(current.data);
    current = current.next;
}

If you change a playlist from an array to a tree, every loop that iterates it must change too. Iterator removes that dependency.

Core Roles in Iterator

Iterator Interface

Declares hasNext() (is there a next item?) and next() (give me the next item). Some versions also include remove().

Concrete Iterator

Implements the interface for a specific collection type. Tracks the current position.

Aggregate (Collection) Interface

Declares a method that returns an Iterator. The collection promises it can be iterated.

Concrete Aggregate

Implements the collection and creates the matching Iterator when asked.

A Diagram in Plain Text

+-------------------+       creates       +-------------------+
| Playlist          |-------------------> | PlaylistIterator  |
| (Concrete Aggr.)  |                     | (Concrete Iter.)  |
|                   |                     |                   |
| songs[]           |                     | index: 0          |
| createIterator()  |                     | hasNext()         |
+-------------------+                     | next()            |
                                          +-------------------+
                                                   |
                    Client code                    |
                    +---------+    uses interface  |
                    |  Player |------------------->| <<Iterator>>  |
                    +---------+                    | hasNext()     |
                                                   | next()        |
                                                   +---------------+

The Player only talks to the Iterator interface. Swap the collection from an array to a tree — the Player code never changes.

Code Example: Music Player

Step 1 — The Iterator Interface

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

Step 2 — The Aggregate Interface

interface Iterable<T> {
    Iterator<T> createIterator();
}

Step 3 — The Concrete Collection

class Playlist implements Iterable<String> {
    private String[] songs;
    private int count = 0;

    public Playlist(int capacity) {
        songs = new String[capacity];
    }

    public void addSong(String song) {
        songs[count++] = song;
    }

    public String getSong(int index) {
        return songs[index];
    }

    public int getCount() {
        return count;
    }

    public Iterator<String> createIterator() {
        return new PlaylistIterator(this);
    }
}

Step 4 — The Concrete Iterator

class PlaylistIterator implements Iterator<String> {
    private Playlist playlist;
    private int index = 0;

    public PlaylistIterator(Playlist playlist) {
        this.playlist = playlist;
    }

    public boolean hasNext() {
        return index < playlist.getCount();
    }

    public String next() {
        return playlist.getSong(index++);
    }
}

Step 5 — Use It

Playlist morning = new Playlist(5);
morning.addSong("Song A");
morning.addSong("Song B");
morning.addSong("Song C");

Iterator<String> it = morning.createIterator();
while (it.hasNext()) {
    System.out.println("Playing: " + it.next());
}

// Output:
// Playing: Song A
// Playing: Song B
// Playing: Song C

Multiple Iterators on the Same Collection

You can create several independent iterators over the same collection. Each iterator tracks its own position. Changing one does not affect the others.

Iterator<String> iter1 = morning.createIterator();
Iterator<String> iter2 = morning.createIterator();

iter1.next();  // iter1 at Song B
iter1.next();  // iter1 at Song C

iter2.next();  // iter2 still at Song B (independent)

Built-in Iterator Support

Java

Java's java.util.Iterator interface and the enhanced for-each loop use this pattern. Every collection in java.util implements Iterable, so the pattern is already built in:

List<String> names = new ArrayList<>();
names.add("Alice");
names.add("Bob");

// for-each uses the iterator under the hood
for (String name : names) {
    System.out.println(name);
}

Python

Python uses __iter__ and __next__ dunder methods. Any object that implements them works in a for loop:

class CountDown:
    def __init__(self, start):
        self.current = start

    def __iter__(self):
        return self

    def __next__(self):
        if self.current <= 0:
            raise StopIteration
        val = self.current
        self.current -= 1
        return val

for n in CountDown(3):
    print(n)  # 3, 2, 1

Types of Iterators

Forward Iterator

Moves in one direction from start to end. The most common type.

Bidirectional Iterator

Moves both forward and backward. Linked lists support this with previous() alongside next().

Filtered Iterator

Wraps another iterator and skips items that do not match a condition. The underlying collection is not modified.

class EvenIterator implements Iterator<Integer> {
    private Iterator<Integer> source;
    private Integer next;

    public EvenIterator(Iterator<Integer> source) {
        this.source = source;
        advance();
    }

    private void advance() {
        next = null;
        while (source.hasNext()) {
            int val = source.next();
            if (val % 2 == 0) {
                next = val;
                break;
            }
        }
    }

    public boolean hasNext() { return next != null; }

    public Integer next() {
        Integer val = next;
        advance();
        return val;
    }
}

Real-World Uses

  • Database cursors: A query returns a cursor (iterator) that delivers one row at a time instead of loading the entire result into memory.
  • File system traversal: Walking a directory tree delivers one file path at a time via an iterator.
  • Pagination: An API iterator fetches one page of results and automatically requests the next page when you exhaust the current one.
  • Stream processing: Java Streams and Python generators are lazy iterators — they produce values only when requested.

Common Mistakes to Avoid

Modifying a Collection While Iterating

Adding or removing items during iteration causes unpredictable behavior. Java throws a ConcurrentModificationException. Collect items to add or remove in a separate list, then apply the changes after iteration completes.

Not Releasing Iterators that Hold Resources

Database cursors and file iterators hold resources. Always close them when done. Use try-with-resources in Java or context managers in Python.

Assuming Iteration Order

Some collections (like HashSet) do not guarantee order. If order matters, choose a collection that preserves it (LinkedHashSet, List) or sort explicitly before iterating.

Benefits and Trade-offs

Benefits

  • Client code works with any collection type using the same interface.
  • Internal structure of the collection stays hidden.
  • Multiple independent iterators can traverse the same collection simultaneously.
  • Lazy iterators process one item at a time, keeping memory use low for large collections.

Trade-offs

  • Overkill for simple collections you access by index once.
  • Custom iterators add classes to the codebase.
  • Stateful iterators can cause bugs if shared between threads without synchronization.

Quick Summary

Iterator provides a uniform way to walk through any collection without knowing its internal structure. Define hasNext() and next(), and the client code works regardless of whether the data sits in an array, linked list, tree, or remote data source. Most modern languages build this pattern directly into their collection frameworks.

Leave a Comment