Iterator and Template Method

Topics Covered

The Iterator Pattern

Why Iterator Matters

The Iterator Interface

Internal vs External Iterators

Custom Iterators and Traversal

Multiple Iterators for One Collection

Lazy Evaluation and Large Collections

Fail-Fast vs Snapshot Iterators

The Template Method Pattern

How It Works

Why Template Method Matters

Defining Algorithm Skeletons

Hook Methods

Template Method vs Strategy

Practice

Imagine you have a list of products, a binary tree of file system entries, and a graph of social connections. Each stores data differently: contiguous array, linked nodes, adjacency lists. But the code that processes each collection wants the same thing: give me the next element, and tell me when you are done. The Iterator pattern solves this by providing a uniform interface, hasNext() and next(), that works identically regardless of the underlying data structure.

Iterator traversing different collection types through the same interface

Why Iterator Matters

Without iterators, client code must know the internal structure of every collection it processes. Traversing an array requires an index variable and a length check. Traversing a linked list requires following node pointers. Traversing a tree requires a stack or recursion. This means the code that uses the collection is tightly coupled to how the collection stores data: change the storage, break the client.

The Iterator pattern decouples traversal from storage. The collection creates an iterator object that knows how to walk through its elements. The client talks only to the iterator interface. If you swap the collection from an array to a tree, the client code does not change at all.

The Iterator Interface

Every iterator implements two core methods:

 
1interface Iterator<T> {
2    hasNext(): boolean   // Are there more elements?
3    next(): T            // Return the next element
4}

The collection itself implements a factory method that creates the appropriate iterator:

 
interface IterableCollection<T> {
    createIterator(): Iterator<T>
}

This separation means the collection owns its traversal logic but exposes it through a standard contract. An ArrayList creates an ArrayIterator that walks indices 0 to N-1. A LinkedList creates a LinkedListIterator that follows node pointers. A BinaryTree creates a TreeIterator that uses a stack for depth-first traversal. The client code is identical for all three.

Key Insight

The real power of Iterator is not convenience: it is that you can change a collection's internal structure without touching any code that reads from it. This is the Open/Closed Principle in action: the collection is open for extension (new storage formats) but closed for modification (client code stays unchanged).

Internal vs External Iterators

There are two flavors. An external iterator gives the client control: the client calls next() in a loop and decides when to stop, skip, or break. This is what most people think of as an iterator.

 
1iterator = collection.createIterator()
2while iterator.hasNext():
3    item = iterator.next()
4    if item.price > 100:
5        print(item)

An internal iterator gives the collection control: you pass a function, and the collection applies it to every element. The forEach, map, and filter methods in modern languages are internal iterators.

 
collection.forEach(item => {
    if (item.price > 100) print(item)
})

External iterators are more flexible: you can iterate two collections in lockstep, break early based on complex conditions, or interleave iterations. Internal iterators are simpler: less boilerplate, less chance of off-by-one errors, and the collection can optimize traversal internally (parallel execution, lazy evaluation).