If you remember five things
The whole pattern in one glance. Everything below defends these five calls.
- Intent. Walk a collection element by element without exposing how it is stored (array, tree, linked list, remote pages).
- Two methods.
hasNext()asks "anything left?" andnext()hands over the next item and advances the cursor. That is the entire surface. - Cursor lives in the iterator, not the collection. That is why two loops over the same collection do not stomp on each other.
- Program to the interface. The caller depends on
Iterator, never onPlaylistIterator, so you can swap in a shuffled or reversed walk with zero caller changes. - The classic gotcha. Mutating the collection mid-iteration. Either snapshot a copy or fail fast, never silently return garbage.
1The problem it solves#
Start with the pain, because the pattern only makes sense once you feel it.
- Say I hand you a
Playlist. Internally it might be an array, a linked list, or a tree sorted by play count. - You want to loop over the songs. How?
- The naive answer: expose the internal list. Add a
getSongs()that returns the rawArrayList, and the caller writes a normalforloop.
That looks fine for about ten seconds. Here is what it actually costs:
- Storage leaks into the caller. They now know I use an
ArrayList. Switch to a tree tomorrow and their code breaks. One storage change, N call sites to fix. - Your internals are mutable from the outside. Anyone can grab that list and call
.clear(). You handed out your private state. - Every collection becomes its own dialect. Three collections stored three ways (songs, playlists, podcasts) means the caller learns three traversal styles.
The deeper problem is that two jobs got tangled together.
- Traversal logic and storage logic are now mixed.
- That is an encapsulation leak, and it breaks the Single Responsibility Principle: the caller is suddenly responsible for both reading data and knowing the layout.
- I want to let people walk my collection without telling them how it is built, and without letting them poke at my guts.
That is the gap. The Iterator pattern fills it.
Separate "how you move through a collection" from "how the collection stores things." Say that sentence out loud before you draw anything, and the rest of the interview is just you proving you meant it.
2The idea (mental model)#
Here is how I explain it at the whiteboard.
- A collection is a book. An iterator is a bookmark.
- The book does not know where you are in it. The bookmark does.
- Two bookmarks can sit in the same book on different pages, and neither confuses the other.
So instead of the caller managing an index, I hand them a small object whose entire job is "remember where we are and give me the next item." Two methods:
hasNext(): is there anything left?next(): give me the next thing, and move the bookmark forward.
What that buys you:
- The caller never sees the array, the index, or the node pointers.
- They just keep asking "got more?" and "give me the next one" until the answer is no.
- The collection stays sealed. The traversal state lives in a separate, throwaway object.
Storage is the collection's business. Position is the iterator's business. Mix them and the pattern is gone.
3Structure#
There are four roles, and naming them cleanly is half the interview.
- Iterator (interface): declares
hasNext()andnext(). - ConcreteIterator: implements it and holds the cursor state for one specific collection.
- IterableCollection (the aggregate, an interface): declares something like
createIterator(). - ConcreteCollection: implements
createIterator()and knows how to build the right iterator for its own layout.
Why a separate object and not a method on the collection:
- The collection hands out a fresh iterator every time.
- Each one tracks its own position, so there is no shared state and no bug when two parts of your code iterate the same collection.
- This is programming to an interface, not an implementation: the caller depends on
Iterator, never onPlaylistIterator.
Playlist creates the PlaylistIterator (solid association), but the caller only ever talks to the Iterator interface (dashed "implements"). The concrete iterator type never escapes into client code.4Implementation in Java#
I will build the playlist, kept tight so the design shows through.
public interface Iterator<T> {
boolean hasNext();
T next();
}
public interface IterableCollection<T> {
Iterator<T> createIterator();
}
The collection. The internal storage is an ArrayList, and nobody outside gets to see it.
public class Playlist implements IterableCollection<Song> {
private final List<Song> songs = new ArrayList<>();
public void add(Song song) {
songs.add(song);
}
@Override
public Iterator<Song> createIterator() {
return new PlaylistIterator(songs);
}
}
The iterator. This is the object that holds the cursor, and the only object that does.
public class PlaylistIterator implements Iterator<Song> {
private final List<Song> songs;
private int position = 0;
public PlaylistIterator(List<Song> songs) {
this.songs = songs;
}
@Override
public boolean hasNext() {
return position < songs.size();
}
@Override
public Song next() {
if (!hasNext()) {
throw new NoSuchElementException("No more songs");
}
return songs.get(position++);
}
}
The caller. Clean, and completely blind to how Playlist stores anything.
Playlist playlist = new Playlist();
playlist.add(new Song("Midnight City"));
playlist.add(new Song("Teardrop"));
Iterator<Song> it = playlist.createIterator();
while (it.hasNext()) {
Song song = it.next();
System.out.println(song.getTitle());
}
Playlist should play in shuffled order. How much of that while loop do I have to rewrite?Reveal the answer
ShuffledIterator that walks a randomized order and return it from createIterator(). The caller still depends only on the Iterator interface, so the loop body is untouched. That is the Open/Closed Principle in action: new traversal behavior added, zero caller code modified. If you can say that sentence, you understand why the iterator is a separate object.5A real-world example#
You have used this a thousand times. Java's java.util.Iterator is exactly this pattern.
- Every enhanced
forloop is sugar over it:
for (Song song : playlist) { ... }
- The compiler calls
iterator()under the hood and driveshasNext()andnext()for you. - That is why you loop over an
ArrayList, aHashSet, and aLinkedListwith identical syntax even though their internals are wildly different (array, hash buckets, node pointers). One loop body, three storage layouts.
Where it earns its keep in real systems:
- Paginated API clients. You write
for (Record r : client.allRecords())while the iterator quietly fetches page 2 only after you exhaust page 1. Lazy loading hides insidenext()and the caller never thinks about pages. - Database result sets. A
ResultSetcursor is an iterator over rows you have not loaded yet. Stream a million rows without a million rows sitting in heap. - Tree and graph traversal. In a file-system explorer you can expose a
DepthFirstIteratorand aBreadthFirstIteratorover the same directory tree. Same collection, two walk orders, same loop body.
That last one is the move I wait for. Ask me to design a file explorer or an org-chart browser, then offer two iterators over one structure, and I know you actually understand the separation instead of reciting the GoF book. Reaching for it unprompted is worth more than a perfect class diagram.
6When to use it, and when not to#
Reach for it when:
- You want to hide how a collection is stored but still let people walk it.
- You need more than one way to traverse the same structure: forward, reverse, filtered, shuffled.
- You are building a custom collection and want it to feel native, so make it
Iterableand it drops into afor-each. - Traversal should be lazy, computing or fetching the next item only when asked.
Skip it when:
- You are working with a plain
ListorMapthat already gives you iteration. Do not wrapArrayListin your own iterator class to look clever. The standard library already solved this. - The collection is tiny, private, and only you touch it. A direct index loop is fine.
Rule of thumb I use:
- If the collection crosses a module or API boundary, an iterator is probably worth it.
- If it stays inside one class, usually not. The pattern is about boundaries, and with no boundary there is no payoff.
7Common mistakes#
Calling next() without checking hasNext().
- You blow past the end and either get an exception or, worse, silently return garbage.
- Always guard
next()so it throws clearly when empty. - That is why the example throws a
NoSuchElementExceptionwith a message, on purpose.
Leaking the collection through the iterator (the classic gotcha).
PlaylistIteratorholds a reference to the samesongslist.- Add items to the playlist mid-iteration and behavior gets weird.
- Java's built-in iterators defend this with fail-fast checks that throw
ConcurrentModificationException. - For an interview, name the two fixes: snapshot a copy at
createIterator()time, or track a modification count and detect the change. Do not ignore it.
Putting traversal state on the collection instead of the iterator.
- The anti-pattern: a
private int currentIndexonPlaylistitself, withfirst()andnext()methods on the collection. - Now you can only have one traversal at a time, and two loops over the same collection stomp on each other.
- The whole point is that state lives in the iterator, so each loop gets its own cursor.
Making the iterator do too much.
- It walks and returns. That is it.
- It should not filter, sort, and transform all at once unless that is literally its named job.
- Keep
hasNextandnextsimple.
Forgetting to implement Iterable in Java.
- If you build a custom collection, implement
java.lang.Iterableso it works withfor-each. - Rolling your own
createIterator()when the language hands youiterator()for free just means your collection will not play nicely with the rest of Java.
Putting the cursor on the collection, then freezing when I ask "what happens if two parts of my code loop over this at the same time?" If your Playlist owns currentIndex, the honest answer is "they corrupt each other," and that admission tells me you have not actually used this pattern. State up front that position lives in the iterator, and this trap never opens.
8Where it shows up in interviews#
Iterator rarely shows up as "design an iterator." It hides inside a bigger problem, and the interviewer watches whether you reach for it naturally.
- "Design an in-memory collection or custom list." The follow-up is always "how do people loop over it?" The right answer is make it
Iterable. - "Design a file system." Offer different traversal orders as iterators. That earns points.
- "Design a card game or deck." Dealing cards one at a time is basically
next(). - As a building block in other patterns. Composite pairs with it constantly (you iterate a tree of nodes). Observer sometimes iterates its subscribers.
If you are asked directly, run this order:
- Start with the mental model: the collection is the book, the iterator is the bookmark.
- Write the two interfaces, then the concrete pair.
- State up front that traversal state lives in a separate object, not the collection, and say why.
That one sentence tells the interviewer you understand the pattern instead of memorizing it. This is an Easy pattern, so the bar is not "can you write it." The bar is "do you know why it exists." Lead with the why.
"What happens if the collection is modified while I am iterating it?"
"Why not just put next() and hasNext() directly on the Playlist?"
"How would you support reverse or filtered traversal?"
"How does this handle lazy or infinite sequences?"
"In Java, Iterator versus Iterable: what is the difference?"
What you can now explain in the room
- The one-line intent: walk a collection without exposing how it stores its elements.
- Why the cursor must live in the iterator and not on the collection.
- The four roles and how they map onto a clean Java `Playlist` plus `PlaylistIterator`.
- Why programming to the `Iterator` interface lets you swap in shuffled or reversed walks with zero caller changes.
- The classic gotcha (mutating mid-iteration) and the two fixes: snapshot or fail-fast.
- When to skip the pattern entirely, and where it quietly powers Java's `for-each`, paginated clients, and result sets.
Next, look at the Composite pattern (iterators walk composite trees beautifully) and the Observer pattern, where you will often iterate a list of subscribers.
Get a prep plan built around your Iterator Pattern interview
Tell us the role and your interview date. We will send back a dated, day by day plan. Free.