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.
