2026-05-28

Iterator Pattern

When you touch object-oriented programming languages, you are likely to encounter the GoF Design Patterns. In my case, I first encountered design patterns when I read Introduction to Design Patterns in Java in high school.

GoF (Gang of Four) design patterns refer to 23 implementation patterns in programming. In modern times, some design patterns are absorbed into (standard) libraries, replaced by technologies like DI (Dependency Injection) containers, and are used without being consciously recognized as design patterns. Others are reinterpreted by using more advanced language features.

I am writing this article to organize my own understanding. I also think that since this kind of "classical" topic is becoming less common as people write fewer of these kinds of articles and as Generative AI becomes mainstream, it is worthwhile to leave it as a written record.

Prerequisites

This article explains design patterns using Java. So it is enough to grasp the flavor of Java code. If you have touched a modern programming language, you should be able to read it even without Java experience.

Note

We use Java 25.

To explain the Iterator pattern, I intentionally avoid writing code that uses java.util.Iterator or java.util.stream.Stream.

Because I included samples using features supported by Java 25, some snippets may appear novel to people only familiar with Java 17 or Java 21.

The 23 GoF Patterns

The GoF design patterns refer to the 23 patterns introduced in Design Patterns: Elements of Reusable Object-Oriented Software (Addison-Wesley Professional Computing Series). The four authors are called the Gang of Four, and thus the 23 patterns are called the patterns of GoF.

Classification

The 23 GoF patterns are broadly classified into three groups.

  • Creational patterns
    • Factory Method
    • Abstract Factory
    • Builder
    • Prototype
    • Singleton
  • Structural patterns
    • Adapter
    • Bridge
    • Composite
    • Decorator
    • Facade
    • Flyweight
    • Proxy
  • Behavioral patterns
    • Chain of Responsibility
    • Command
    • Interpreter
    • Iterator
    • Mediator
    • Memento
    • Observer
    • State
    • Strategy
    • Template Method
    • Visitor

This article explains the behavioral pattern Iterator.

Ways to access data

Before explaining the Iterator pattern, let’s quickly review representative data structures. Since Iterator is a pattern for providing data access, it’s important to understand data access approaches without it first.

Array

One of the most common data structures supported by many programming languages is an array. When you want to access elements from the first one onward, a common approach is to increment an index in a for loop.

List

The next familiar structure for many people is a list. There are several ways to implement a list, but in Java ArrayList is common. If it implements the List interface, you can operate through a common interface. Element access is still done by incrementing an index, as with arrays.

The difference from arrays is that list size is obtained by List#size() (arrays use length).

Map

Another structure you often encounter is a [(key-value) map], also called an associative array, or dictionary.

Here we iterate through key and value sets one by one. We get the key set, convert it to an array, then fetch keys by index and retrieve each value from the map.

If you are familiar with Java, you might think of calling Map#values() directly, but remember that this article is about Iterator.

Set

Since set was not covered earlier due to familiarity order, let’s check set (collection).

As this is simpler than map, no extra explanation is needed.

Tree

You may not use trees often, but it is a representative data structure. Here I implement a binary tree where nodes (leaf or branch) hold values, and print every node value.

A simple binary tree can be implemented as follows.

BinaryTree.javajava

For people who have not seen newer Java, this may look unfamiliar. Run this together with jshell to verify it works.

jshelljava
jshelljava

To output each node value once:

Again, if you have not touched recent Java, pattern matching in switch may feel new.

Iterator Pattern

Now we move to the main topic. In the previous section, we saw different access code for various data structures. Because those codes are different, when you want to sum integers in each structure, you need structure-specific logic.

For arrays, the code would be:

For a binary tree:

In both cases, the operation is simply iterating each structure’s values and summing them. You can also see only the highlighted lines differ.

This raises the natural question:

Question

Can we abstract this so it doesn’t depend on the data structure?

One of the answers is the Iterator pattern.

Iterator pattern provides a way to access internal data one element at a time while hiding the structure. Anything can be a data structure: array, list, set, map, tree, or even a plain class, including empty cases. This is because the mechanism it provides is structure-independent access to data.

It is often explained with an interface like this.

Iterator.javajava

Although Java’s Iterator has Iterator#remove and Iterator#forEachRemaining defined, in general an Iterator pattern requests only two methods:

  • hasNext: check if a next element exists
  • next: return the next element

That is exactly what this Iterator interface expresses.

The class providing Iterators is called Aggregate. Aggregate refers to the class that returns Iterators, and is often expressed like this (no need to memorize it closely).

Aggregate.javajava

In design pattern terminology, this is Aggregate; in Java's standard library, Iterable corresponds to it. Implementing Iterable also gives the advantage of using Java’s enhanced for-loop, so initially you can treat Iterable as Aggregate.

The relationship between Aggregate, Iterator, and concrete implementations is as follows:

ConcreteAggregate creates a ConcreteIterator when Aggregate#iterator() is called. The created iterator uses internal state (cursor) and the original ConcreteAggregate data to return elements one by one via hasNext and next. From the caller side, we only interact through the abstractions Aggregate/Iterator, so the way to access data is the same whether it is an array, binary tree, or paginated web API data.

How to use Iterator

Basic usage of Iterator is as follows.

By using Iterator pattern, you can check whether there is a next element (hasNext()) and get it (next()) using a common interface, so you don't need to care how the data is structured. Let's experience this with Java standard Iterator.

Define a function display to print all values from an iterator.

This corresponds to the "display values" part in earlier examples. Originally, each data structure needed different access logic, but with Iterator pattern you can do the following using just one call with a java.util.Iterator instance.

Here it is a simple program that just prints values in order, but you can see how it can abstract typical value-by-value logic.

Using Iterator pattern like this allows writing code to access one value at a time from any data structure without depending on how the data is held.

Practical example of the Iterator pattern

Next, let’s apply Iterator pattern to a binary tree. Earlier we obtained an iterator by calling Iterable#iterator() from the standard library, but for our custom BinaryTree class we need to implement an iterator method.

Instantiate BinaryTree:

Call BinaryTree#iterator() and loop:

The output is depth-first pre-order, as when implementing the BinaryTree traversal earlier.

The important point is that the code shape is the same as enumerating values from arrays, lists, maps, and sets through Iterator. In the previous section we used Java standard Iterator, and here we use a custom Iterator class. So you can’t directly use a shared method like display unless BinaryTree implements java.lang.Iterable.

A more realistic Iterator example

So far we only extracted values from a collection, which might feel somewhat artificial in real engineering. In practice many engineers may rarely write a BinaryTree class like this. For those readers, here is a slightly more realistic example.

Suppose we have a hypothetical web API returning JSON that can be mapped to the following POJO.

ApiResponse.javajava

This API has a limit, so it returns only up to 100 items in ids (※1). Suppose you want to do something with all IDs from this API. The exact purpose is not important here, so I will just print them to standard output like previous examples.

Goal

Print all IDs retrieved from the web API to standard output.

In reality, with a Java API this would likely be mapped with a library such as Jackson, but here we use a dummy API client to mock retrieval.

Assume pagination uses page size and page count. Offset or cursor models are also possible (※2). Also assume page numbers start at 1 (※3). Since this API is unhelpful, we must judge previous/next pages by comparing current page and totalPage, or by checking whether the response is empty (※4).

Under these conditions, we want code that achieves the goal.

Question

Before checking the Iterator-based implementation I show below, pause and think about what implementation you would write.

ApiResponse Iterator

Before enumerating all IDs in ids, first implement Aggregate on ApiResponse so IDs can be iterated.

ApiResponse.javajava

This implementation is simple: it stores an index in the iterator field and returns the index element of ids while incrementing it on each next call. ApiResponse IDs can now be accessed in order through the Iterator interface. A benefit is that if later ids is stored in a data structure other than List, callers need no change.

Enumerating IDs

Now add a method to ApiClient that returns an Iterator for all IDs.

Because ApiResponse#iterator() is implemented, we no longer need separate logic to access each ids page. The logic is: if iterator is null, fetch the next page, set an iterator from the response IDs, and increment page for next time. If iterator is not null, check hasNext on it.

next mainly returns iterator.next(). When there is no next element in that iterator, it sets iterator to null, so next hasNext fetches the next page.

Using the returned iterator from allIds, caller code looks like this:

This accomplishes the goal and avoids extra concerns.

What should be the concern? It is only this: "enumerate all IDs." Everything else should not be part of the core implementation logic.

What does "not part" mean? A careful reader will notice the numbered notes (※1~※4) in the API specification.

  • This API returns at most 100 items at once in ids (※1)
  • The sample assumes page-size and page-count pagination, but offset and cursor forms also exist (※2)
  • Assume page numbers start from 1 (※3)
  • Because this API is not convenient, whether there are previous/next pages is judged by comparing current page and totalPage, or by detecting an empty response (※4)

These API-level constraints should not be explicitly present in the core logic that achieves the goal, though they must be considered during implementation. Such details should be separated from the core logic. Put differently, even if API specs change, code that performs the core "enumerate all IDs" should ideally remain unchanged.

This is exactly what Iterator pattern achieves. Even if pagination style changes, adjust allIds internal iterator behavior, and you can leave the loop using hasNext and next untouched.

Enhanced for-loop

Although this is getting into Java language features, if we explain Iterator in Java, we must talk about the enhanced for loop. Its syntax is:

The enhanced for loop is not a special syntax for arrays or List alone, but is available for subtypes of java.lang.Iterable.

In the BinaryTree example, change Aggregate to Java standard java.lang.Iterable. Then BinaryTree#iterator() should return java.util.Iterator. You only need to change Aggregate to java.lang.Iterable in code.

Use it as follows:

jshelljava

You can see it works like this:

jshelljava

Instances implementing java.lang.Iterable can be iterated using the enhanced for loop. Because in recent coding styles loops are often avoided, actual opportunities may be fewer. Benefits include not needing to expose Iterator operation on the surface, and avoiding mistakes around Iterator#hasNext() and Iterator#next().

Side effects of Iterator

Some may ask whether Iterator#hasNext() having side effects is a problem.

In many programming styles, side effects are avoided as much as possible. However, Iterator pattern by design can update internal state or fetch external data on hasNext() and next(). From hasNext semantics, you often need to confirm existence somehow when asking hasNext(), and this can cause side effects.

If data is fully in memory, checking without side effects is often possible. But when values are not preloaded and require external retrieval, you cannot guarantee existence until retrieving the data. Also Iterator#next() must return a different element each time, so you must keep state about where you are. Therefore side effects are tightly coupled with Iterator in practice.

Summary

This article introduced the Iterator pattern, one of GoF’s 23 patterns. Among online articles, this one tries to give enough detail. Many existing articles either explain standard-library Iterator#iterator() usage or simple BookShelf-like examples from Introduction to Design Patterns in Java.

But as this article shows, the key advantage is an abstraction with two methods:

  • check whether next element exists (hasNext)
  • return next element (next)

This allows structure-agnostic data access and implementation. To understand it, it is best to implement an interface like Iterable.

As a closing exercise, implement an Iterator that recursively traverses all files in a directory, which is a representative tree structure in programming. In modern Java, this is possible with Files#walk, but try implementing it without relying on Stream for learning.

増補改訂版 Java言語で学ぶデザインパターン入門 Kindle版

増補改訂版 Java言語で学ぶデザインパターン入門 Kindle版

Amazon アソシエイトについて

この記事には Amazon アソシエイトのリンクが含まれています。Amazonのアソシエイトとして、SuzumiyaAoba は適格販売により収入を得ています。