Iterator Pattern explained simply
Thilan Dissanayaka Software Architecture February 12, 2020

Iterator Pattern explained simply

How do you traverse a collection without exposing its internal structure? You might have an array, a linked list, a tree, or a hash map – but the client code just wants to go through the elements one by one. It shouldn’t have to care about indexes, pointers, or bucket sizes. That is exactly the problem the Iterator Pattern solves.

What is the Iterator Pattern?

  • Provides a way to access elements of a collection sequentially without exposing its underlying representation.
  • Separates the traversal logic from the collection itself.
  • Lets you define multiple traversal strategies for the same collection.

In short:

Give me the next element. I don’t care how you store them.

Real-Life Analogy

Think about a TV remote. You press “Next Channel” and “Previous Channel” to surf through channels. You have no idea how the TV internally stores those channels – maybe it is a sorted list, maybe it is a frequency table, maybe it is pulling from a satellite feed. You don’t care. You just press the button and the next channel shows up.

That is the Iterator Pattern. The remote is your iterator. The TV’s internal channel storage is the collection. And you, the viewer, are the client code that just wants to move through things without worrying about the internals.

Class Diagram

Structure

  • Iterator (Interface): Declares hasNext() and next() methods for traversing elements.
  • ConcreteIterator (NotificationBarIterator): Implements the traversal logic. Keeps track of the current position internally.
  • Aggregate / IterableCollection (Interface): Declares a method to create an iterator – createIterator().
  • ConcreteAggregate (NotificationBar): Stores elements in some internal data structure and returns an iterator when asked.

The client never touches the internal array, linked list, or whatever the collection uses. It only talks to the iterator.

Example in Java – Building a Notification Inbox

Let’s say you are building a notification system. The NotificationBar stores notifications internally in a plain array (for performance reasons, maybe). But the rest of your application just wants to loop through them without knowing that detail.

Notification Class

public class Notification {
    private String message;
    private String type;
    private long timestamp;

    public Notification(String message, String type, long timestamp) {
        this.message = message;
        this.type = type;
        this.timestamp = timestamp;
    }

    public String getMessage() { return message; }
    public String getType() { return type; }
    public long getTimestamp() { return timestamp; }

    @Override
    public String toString() {
        return "[" + type + "] " + message;
    }
}

A simple POJO. Nothing fancy.

Iterator Interface

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

Two methods. That is all an iterator needs. hasNext() tells you if there are more elements, and next() gives you the current one and moves forward.

NotificationCollection Interface (Aggregate)

public interface NotificationCollection {
    Iterator<Notification> createIterator();
}

Any collection that wants to be iterable just needs to provide a way to create an iterator. The collection decides which iterator to return.

NotificationBar – The Concrete Collection

public class NotificationBar implements NotificationCollection {
    private Notification[] notifications;
    private int count = 0;
    private static final int MAX_SIZE = 100;

    public NotificationBar() {
        notifications = new Notification[MAX_SIZE];
    }

    public void addNotification(Notification notification) {
        if (count < MAX_SIZE) {
            notifications[count] = notification;
            count++;
        }
    }

    @Override
    public Iterator<Notification> createIterator() {
        return new NotificationBarIterator(notifications, count);
    }
}

Internally, it uses a fixed-size array. The client code never sees that. It just calls createIterator() and gets back something it can loop through.

NotificationBarIterator – The Concrete Iterator

public class NotificationBarIterator implements Iterator<Notification> {
    private Notification[] notifications;
    private int position = 0;
    private int count;

    public NotificationBarIterator(Notification[] notifications, int count) {
        this.notifications = notifications;
        this.count = count;
    }

    @Override
    public boolean hasNext() {
        return position < count;
    }

    @Override
    public Notification next() {
        Notification notification = notifications[position];
        position++;
        return notification;
    }
}

This is where the traversal logic lives. It knows how to walk through an array using a position index. If the collection switched to a linked list tomorrow, you would write a different iterator – but the client code wouldn’t change at all.

Putting It All Together

public class MainProgram {
    public static void main(String[] args) {
        NotificationBar bar = new NotificationBar();

        bar.addNotification(new Notification(
            "New login from Chrome on Windows", "security", System.currentTimeMillis()));
        bar.addNotification(new Notification(
            "Your order has been shipped", "order", System.currentTimeMillis()));
        bar.addNotification(new Notification(
            "John commented on your post", "social", System.currentTimeMillis()));
        bar.addNotification(new Notification(
            "Password changed successfully", "security", System.currentTimeMillis()));

        // client code doesn't know about the internal array
        Iterator<Notification> iterator = bar.createIterator();

        while (iterator.hasNext()) {
            Notification n = iterator.next();
            System.out.println(n);
        }
    }
}

Output:

[security] New login from Chrome on Windows
[order] Your order has been shipped
[social] John commented on your post
[security] Password changed successfully

The main method has no idea that notifications are stored in an array. It could be a linked list, a database cursor, or a file stream. As long as the iterator implements hasNext() and next(), the client code stays exactly the same.

Key Components

  • Iterator Interface: The contract – hasNext() and next().
  • Concrete Iterator: Implements the traversal for a specific data structure.
  • Aggregate Interface: Declares createIterator() so collections can produce iterators.
  • Concrete Aggregate: The actual collection that holds data and knows which iterator to create.
  • Client: Uses only the iterator interface. Never touches the collection’s internals.

When to Use the Iterator Pattern

  • When you want to traverse a collection without exposing its internal representation.
  • When you need to support multiple traversal strategies (forward, reverse, filtered) over the same collection.
  • When you want a uniform interface for iterating over different types of collections (arrays, lists, trees).
  • When you want to decouple the traversal algorithm from the collection structure.

Advantages and Disadvantages

Advantages:

  • Single Responsibility: The collection stores data. The iterator traverses it. Clean separation.
  • Open/Closed Principle: You can add new iterator types without modifying the collection.
  • Uniform Access: Client code looks the same regardless of the underlying data structure.
  • Multiple Iterators: You can have several iterators traversing the same collection simultaneously, each with its own state.

Disadvantages:

  • Overhead for Simple Collections: If you just have a plain list, writing a full iterator class might be overkill.
  • Extra Classes: Each collection typically needs its own iterator class, which adds to the codebase.
  • Snapshot vs. Live: You need to decide whether the iterator sees modifications made to the collection during iteration. This can get tricky.

Java’s Built-in Iterator

The good news is that you rarely need to build iterators from scratch in Java. The language already bakes this pattern into the Collections Framework.

Java has two key interfaces:

  • java.lang.Iterable<T> – the aggregate. Any class that implements this can be used in a for-each loop.
  • java.util.Iterator<T> – the iterator itself, with hasNext(), next(), and an optional remove().

Here is our notification example rewritten using Java’s built-in support:

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class NotificationBar implements Iterable<Notification> {
    private List<Notification> notifications = new ArrayList<>();

    public void addNotification(Notification notification) {
        notifications.add(notification);
    }

    @Override
    public Iterator<Notification> iterator() {
        return notifications.iterator();
    }
}

And now the client code becomes this:

NotificationBar bar = new NotificationBar();
bar.addNotification(new Notification("New login from Chrome", "security", System.currentTimeMillis()));
bar.addNotification(new Notification("Order shipped", "order", System.currentTimeMillis()));

// enhanced for-each loop -- powered by Iterator under the hood
for (Notification n : bar) {
    System.out.println(n);
}

That for-each loop is just syntactic sugar. Behind the scenes, Java calls iterator() to get an Iterator, then calls hasNext() and next() in a loop. Same pattern, zero boilerplate.

Real-World Use Cases

  • Java Collections Framework: Every List, Set, and Map implements Iterable. The enhanced for loop depends on this pattern.
  • JDBC ResultSet: resultSet.next() is literally an iterator over database rows. You call next() to advance and check the return value to know if there are more rows.
  • Database Cursors: MongoDB cursors, Hibernate ScrollableResults – all iterator-based. They let you process millions of records without loading everything into memory.
  • Stream APIs: Java Streams, C# LINQ, Python generators – all built on lazy iteration. Elements are computed on demand, not upfront.
  • File I/O: BufferedReader.readLine() iterates through a file line by line. Same concept.
  • XML/JSON Parsers: SAX parsers and JsonParser use iterator-style pull parsing to process documents without loading the entire thing.

Wrapping Up

The Iterator Pattern is one of those patterns that is so deeply embedded in modern languages that you use it every day without thinking about it. Every time you write a for-each loop, every time you call .next() on a result set, every time you stream through a collection – you are using this pattern.

The core idea is simple: separate what you are iterating over from how you iterate over it. The collection manages storage. The iterator manages traversal. And the client code stays blissfully ignorant of both.

If you are building custom data structures or need to provide multiple ways to traverse a collection, implementing the Iterator Pattern explicitly gives you clean, decoupled, and flexible code.

ALSO READ
Writing a Shell Code for Linux
Apr 21, 2020 Exploit Development

Shellcode is a small piece of machine code used as the payload in exploit development. In this post, we write Linux shellcode from scratch — starting with a simple exit, building up to spawning a shell, and explaining every decision along the way.

Exploiting a Stack Buffer Overflow on Windows
Apr 12, 2020 Exploit Development

In a previous tutorial we discusses how we can exploit a buffer overflow vulnerability on a Linux machine. I wen through all theories in depth and explained each step. Now today we are going to jump...

Exploiting a  Stack Buffer Overflow  on Linux
Apr 01, 2020 Exploit Development

Have you ever wondered how attackers gain control over remote servers? How do they just run some exploit and compromise a computer? If we dive into the actual context, there is no magic happening....

Basic concepts of Cryptography
Mar 01, 2020 Cryptography

Ever notice that little padlock icon in your browser's address bar? That's cryptography working silently in the background, protecting everything you do online. Whether you're sending an email,...

Common Web Application Attacks
Feb 05, 2020 Application Security

Web applications are one of the most targeted surfaces by attackers. This is primarily because they are accessible over the internet, making them exposed and potentially vulnerable. Since these...

Remote Code Execution (RCE)
Jan 02, 2020 Application Security

Remote Code Execution (RCE) is the holy grail of application security vulnerabilities. It allows an attacker to execute arbitrary code on a remote server — and the consequences are as bad as it sounds. In this post, we'll go deep into RCE across multiple languages, including PHP, Java, Python, and Node.js.

> > >