The Iterator Pattern provides a way to access elements of a collection sequentially without exposing its underlying representation. Whether the data lives in an array, a linked list, a tree, or a paginated API, the iterator gives clients a uniform interface to traverse it: hasNext() and next().
This pattern separates the traversal logic from the collection itself, allowing multiple traversal strategies over the same data structure without modifying the collection class.
You are building a service that fetches results from a paginated REST API. The API returns 20 items per page, and you need to process all items across all pages. The naive approach is to embed the pagination logic directly in your business code — tracking page numbers, checking for the last page, and concatenating results.
Now imagine another part of your codebase also needs to iterate over the same API but with different filtering. You end up duplicating pagination logic everywhere. Worse, if the API changes its pagination scheme (from page-based to cursor-based), you must update every call site.
The Iterator Pattern encapsulates the pagination logic inside an iterator object. Clients simply call hasNext() and next() without knowing whether the data comes from one page or fifty. The iterator handles fetching the next page transparently.
This means your business code stays clean and focused on processing items, while the iterator handles the mechanics of traversal and data fetching.
Single Responsibility Principle. The collection is responsible for storing data. The iterator is responsible for traversing it. Neither takes on the other’s job, making both easier to maintain and extend.
Scenario: An iterator that transparently traverses paginated API results.
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
// Simulated API response
public class Page<T> {
private final List<T> items;
private final int currentPage;
private final int totalPages;
public Page(List<T> items, int currentPage, int totalPages) {
this.items = items;
this.currentPage = currentPage;
this.totalPages = totalPages;
}
public List<T> getItems() { return items; }
public int getCurrentPage() { return currentPage; }
public int getTotalPages() { return totalPages; }
public boolean hasNextPage() { return currentPage < totalPages; }
}
// Simulated API client
public class ProductApiClient {
private final List<List<String>> pages = List.of(
List.of("Laptop", "Keyboard", "Mouse"),
List.of("Monitor", "Headphones", "Webcam"),
List.of("USB Hub", "Desk Lamp")
);
public Page<String> fetchPage(int pageNumber) {
System.out.println(" [API] Fetching page " + pageNumber + "...");
int index = pageNumber - 1;
return new Page<>(pages.get(index), pageNumber, pages.size());
}
}
// Iterator for paginated results
public class PaginatedIterator<T> implements Iterator<T> {
private final ProductApiClient apiClient;
private Page<String> currentPage;
private int itemIndex = 0;
public PaginatedIterator(ProductApiClient apiClient) {
this.apiClient = apiClient;
this.currentPage = apiClient.fetchPage(1);
}
@Override
public boolean hasNext() {
if (itemIndex < currentPage.getItems().size()) {
return true;
}
// Current page exhausted — check if more pages exist
return currentPage.hasNextPage();
}
@Override
@SuppressWarnings("unchecked")
public T next() {
// If current page is exhausted, fetch the next one
if (itemIndex >= currentPage.getItems().size()) {
if (!currentPage.hasNextPage()) {
throw new NoSuchElementException("No more items");
}
currentPage = apiClient.fetchPage(
currentPage.getCurrentPage() + 1);
itemIndex = 0;
}
return (T) currentPage.getItems().get(itemIndex++);
}
}
// Iterable collection that provides the iterator
public class ProductCatalog implements Iterable<String> {
private final ProductApiClient apiClient;
public ProductCatalog(ProductApiClient apiClient) {
this.apiClient = apiClient;
}
@Override
public Iterator<String> iterator() {
return new PaginatedIterator<>(apiClient);
}
}
// Usage
public class Main {
public static void main(String[] args) {
ProductApiClient api = new ProductApiClient();
ProductCatalog catalog = new ProductCatalog(api);
System.out.println("Iterating over all products:\n");
// Clean for-each loop — pagination is invisible
int count = 1;
for (String product : catalog) {
System.out.println(" " + count++ + ". " + product);
}
System.out.println("\nProcessed all products across all pages.");
}
}
Same paginated API traversal in Python using the iterator protocol.
from dataclasses import dataclass
@dataclass
class Page:
items: list[str]
current_page: int
total_pages: int
@property
def has_next_page(self) -> bool:
return self.current_page < self.total_pages
# Simulated API client
class ProductApiClient:
def __init__(self):
self._pages = [
["Laptop", "Keyboard", "Mouse"],
["Monitor", "Headphones", "Webcam"],
["USB Hub", "Desk Lamp"],
]
def fetch_page(self, page_number: int) -> Page:
print(f" [API] Fetching page {page_number}...")
items = self._pages[page_number - 1]
return Page(items, page_number, len(self._pages))
# Iterator for paginated results
class PaginatedIterator:
def __init__(self, api_client: ProductApiClient):
self._api_client = api_client
self._current_page = api_client.fetch_page(1)
self._item_index = 0
def __iter__(self):
return self
def __next__(self) -> str:
# If current page is exhausted, fetch the next one
if self._item_index >= len(self._current_page.items):
if not self._current_page.has_next_page:
raise StopIteration
next_page_num = self._current_page.current_page + 1
self._current_page = self._api_client.fetch_page(next_page_num)
self._item_index = 0
item = self._current_page.items[self._item_index]
self._item_index += 1
return item
# Iterable collection that provides the iterator
class ProductCatalog:
def __init__(self, api_client: ProductApiClient):
self._api_client = api_client
def __iter__(self) -> PaginatedIterator:
return PaginatedIterator(self._api_client)
# Usage
if __name__ == "__main__":
api = ProductApiClient()
catalog = ProductCatalog(api)
print("Iterating over all products:\n")
# Clean for loop — pagination is invisible
for count, product in enumerate(catalog, start=1):
print(f" {count}. {product}")
print("\nProcessed all products across all pages.")
Iterator and Iterable — The foundation of Java’s for-each loop. Every collection in java.util implements Iterable.for loop uses the iterator protocol (__iter__ / __next__). Generators with yield are a concise way to create iterators.ResultSet — Iterates over database query results row by row without loading the entire result set into memory.Page / Slice — Spring Data’s pagination abstractions use the iterator pattern to traverse large datasets from repositories.