Iterator
Iterator is a behavioral pattern that provides a uniform way to step through the elements of a collection one at a time, without revealing whether the collection is stored as an array, a tree, a linked list, or something else.
Problem
A media library might store playlists as a flat array, favorites as a hash set, and a "recently played" history as a fixed-size ring buffer. Every part of the UI that needs to loop over one of these - the shuffle button, the export feature, the search filter - ends up with a different loop shape depending on which structure it's touching.
If the traversal logic is written straight into each collection class, that class now has two jobs: managing its data and knowing every way someone might want to walk through it. Adding a new traversal order, like reverse or shuffled playback, means going back into the collection class again, and switching the underlying storage later breaks every piece of client code that assumed array indexing.
Solution
Iterator moves the traversal logic out of the collection and into a dedicated companion object - the iterator - that knows how to walk that one specific structure and nothing else.
Every iterator, regardless of what it's walking, exposes the same small interface: something like "is there a next element" and "give me the next element." Client code loops using only that interface, so it never has to know or care whether it's iterating an array, a tree, or a ring buffer underneath. A collection can hand out several independent iterators at once, each tracking its own position, and a new traversal order - reverse, shuffled, filtered - is just a new iterator class sitting alongside the existing ones, leaving the collection itself untouched.
When to Use
- Reach for Iterator when the collection's internal layout is genuinely complex and client code shouldn't need to know about it just to loop over the elements.
- Use it to pull nearly identical traversal loops out of several places in the codebase and consolidate them behind one interface.
- It's the right tool when the same client code needs to work across several different, or not-yet-known, kinds of data structures.
Real-World Examples
- **The JavaScript iteration protocol** - any object exposing `Symbol.iterator` automatically works with `for...of`, the spread operator, and array destructuring.
- **Java's `Iterable`/`Iterator` interfaces** - every collection in the Java Collections Framework implements `Iterable`, which is what lets it plug into the enhanced for-loop.
- **Database result cursors** - a JDBC `ResultSet` or a Postgres server-side cursor streams query results row by row instead of loading the whole table into memory at once.
Structure
The Iterator interface fixes the minimal contract for stepping through elements - checking for more and fetching the next one. A ConcreteIterator implements that contract for one particular collection and one particular traversal order, holding its own cursor. The collection side mirrors this split: an IterableCollection interface promises a way to produce a compatible iterator, and a ConcreteCollection implements that by handing back a fresh ConcreteIterator on request.
Participants
Fixes the minimal contract every traversal must offer - typically a way to check for remaining elements and a way to fetch the next one.
In the diagram: Iterator.
Implements one specific traversal strategy over one specific collection, keeping its own cursor so multiple iterators over the same collection don't interfere with each other.
In the diagram: Iterator, the object stepping through the Song one part at a time.
Promises a factory method that produces an iterator matching the collection, without exposing anything about how elements are actually stored.
In the diagram: Song.
Owns the actual storage and produces a properly initialized ConcreteIterator each time client code asks to traverse it.
In the diagram: Song, holding the Intro, Verse, Chorus, and Outro parts.
class Song {
constructor(title) {
this.title = title;
}
}
class Playlist {
constructor() {
this.songs = [];
}
addSong(title) {
this.songs.push(new Song(title));
return this;
}
[Symbol.iterator]() {
let current = 0;
const { songs } = this;
return {
next() {
if (current < songs.length) {
return { value: songs[current++], done: false };
}
return { value: undefined, done: true };
}
};
}
}
const playlist = new Playlist()
.addSong('Intro')
.addSong('Verse')
.addSong('Chorus')
.addSong('Outro');
for (const song of playlist) {
console.log(song.title);
}
console.log([...playlist].map(s => s.title));
Step 1 of 6
Song is the plain element stored in the collection
It just wraps a `title` - the aggregate and its iterator are the parts that carry all the traversal logic, not the elements themselves.
Step 2 of 6
Playlist is the aggregate - a plain array wrapped behind addSong
`this.songs` is a private-ish array, and `addSong` pushes a new `Song` and returns `this` for chaining - clients never touch `songs` directly.
Step 3 of 6
[Symbol.iterator] returns a fresh iterator object with its own cursor
Each call creates a new `current = 0` closed over by a `next()` method - `next()` returns `{ value, done: false }` while there are songs left, then `{ value: undefined, done: true }` - this is the exact protocol `for...of` and spread expect.
Step 4 of 6
The client builds a playlist without knowing about iteration yet
Four chained `addSong` calls populate `playlist.songs` - at this point nothing has iterated over it.
Step 5 of 6
for...of drives the iterator one next() call at a time
The loop calls `playlist[Symbol.iterator]()` once to get an iterator object, then repeatedly calls its `next()` until `done` is `true` - the loop body never touches `songs` or `current` directly.
Step 6 of 6
Spread syntax reuses the exact same iteration protocol
`[...playlist]` calls `[Symbol.iterator]()` again - a brand-new iterator with its own `current`, independent from any earlier `for...of` loop - and collects the results into an array before `.map` runs.
interface IIterator<T> {
hasNext(): boolean;
next(): T;
}
interface IterableCollection<T> {
createIterator(): IIterator<T>;
}
class Song {
constructor(public readonly title: string) {}
}
class PlaylistIterator implements IIterator<Song> {
private current = 0;
constructor(private readonly songs: readonly Song[]) {}
hasNext(): boolean {
return this.current < this.songs.length;
}
next(): Song {
return this.songs[this.current++];
}
}
class Playlist implements IterableCollection<Song> {
private readonly songs: Song[] = [];
addSong(title: string): this {
this.songs.push(new Song(title));
return this;
}
createIterator(): IIterator<Song> {
return new PlaylistIterator(this.songs);
}
}
const playlist = new Playlist()
.addSong('Intro')
.addSong('Verse')
.addSong('Chorus')
.addSong('Outro');
const iterator = playlist.createIterator();
while (iterator.hasNext()) {
console.log(iterator.next().title);
}
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
class Song {
private final String title;
Song(String title) { this.title = title; }
public String getTitle() { return title; }
}
class PlaylistIterator implements Iterator<Song> {
private final List<Song> songs;
private int current = 0;
PlaylistIterator(List<Song> songs) { this.songs = songs; }
public boolean hasNext() { return current < songs.size(); }
public Song next() {
if (!hasNext()) throw new NoSuchElementException();
return songs.get(current++);
}
}
class Playlist implements Iterable<Song> {
private final List<Song> songs = new ArrayList<>();
Playlist addSong(String title) {
songs.add(new Song(title));
return this;
}
public Iterator<Song> iterator() {
return new PlaylistIterator(songs);
}
}
Playlist playlist = new Playlist()
.addSong("Intro")
.addSong("Verse")
.addSong("Chorus")
.addSong("Outro");
for (Song song : playlist) {
System.out.print(song.getTitle() + " ");
}
using System.Collections;
using System.Collections.Generic;
class Song
{
public string Title { get; }
public Song(string title) => Title = title;
}
class Playlist : IEnumerable<Song>
{
private readonly List<Song> _songs = new();
public Playlist AddSong(string title)
{
_songs.Add(new Song(title));
return this;
}
public IEnumerator<Song> GetEnumerator()
{
foreach (var song in _songs)
yield return song;
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
var playlist = new Playlist()
.AddSong("Intro")
.AddSong("Verse")
.AddSong("Chorus")
.AddSong("Outro");
foreach (var song in playlist)
Console.Write(song.Title + " ");
from __future__ import annotations
from collections.abc import Iterator, Iterable
class Song:
def __init__(self, title: str) -> None:
self.title = title
class PlaylistIterator(Iterator[Song]):
def __init__(self, songs: list[Song]) -> None:
self._songs = songs
self._current = 0
def __next__(self) -> Song:
if self._current >= len(self._songs):
raise StopIteration
song = self._songs[self._current]
self._current += 1
return song
def __iter__(self) -> Iterator[Song]:
return self
class Playlist(Iterable[Song]):
def __init__(self) -> None:
self.songs: list[Song] = []
def add_song(self, title: str) -> Playlist:
self.songs.append(Song(title))
return self
def __iter__(self) -> Iterator[Song]:
return PlaylistIterator(self.songs)
playlist = Playlist().add_song('Intro').add_song('Verse').add_song('Chorus').add_song('Outro')
for song in playlist:
print(song.title, end=' ')
print([s.title for s in playlist])
Step 1 of 6
Song is the plain element stored in the collection
It just wraps a `title` - the aggregate and its iterator carry all the traversal logic, not the elements themselves.
Step 2 of 6
PlaylistIterator is a standalone iterator with its own cursor
It stores a reference to `_songs` plus its own `_current` index; `__next__` returns the next `Song` and advances `_current`, or raises `StopIteration` when exhausted; `__iter__` returns `self` so the object satisfies `Iterator`.
Step 3 of 6
Playlist is the aggregate - it only knows how to create an iterator
`add_song` appends to `self.songs` and returns `self` for chaining; `__iter__` returns a brand-new `PlaylistIterator(self.songs)` on every call - the playlist itself has no traversal state.
Step 4 of 6
The client builds a playlist without knowing about iteration yet
Four chained `add_song` calls populate `playlist.songs` - at this point nothing has iterated over it.
Step 5 of 6
for calls __iter__ once, then __next__ repeatedly
`for song in playlist` calls `playlist.__iter__()` to get a fresh `PlaylistIterator`, then calls its `__next__()` until `StopIteration` is raised - the loop body never touches `_current` directly.
Step 6 of 6
The list comprehension reuses the exact same iteration protocol
`[s.title for s in playlist]` triggers another `playlist.__iter__()` call - a brand-new `PlaylistIterator` independent from the `for` loop above, each with its own `_current`.
Click "Run" to execute this code in a sandboxed frame and see console output here.
TypeScript runs as plain JavaScript here - type annotations are stripped, not type-checked.
Click "Run" to execute this code in a sandboxed frame and see console output here.
Advantages
- Collection classes shrink back down to just managing storage, since every traversal algorithm they used to carry moves out into its own iterator class.
- A brand-new traversal order can be introduced as an additional iterator class without touching the collection or any code that already works.
- Because each iterator keeps its own cursor, several independent passes over the same collection can run at once without stepping on each other.
- An iterator can pause mid-traversal and be resumed later, which is exactly what makes lazy, on-demand traversal of huge or infinite sequences practical.
Disadvantages
- For a plain array or list that's only ever looped over one way, wrapping it in a full iterator abstraction adds ceremony without real benefit.
- Going through an iterator's interface can be slower than reaching directly into a specialized structure that supports fast random access.
- An iterator can be invalidated or produce inconsistent results if the underlying collection is mutated while the traversal is still in progress.
Question 1 of 10
In the JavaScript Playlist implementation, why does [Symbol.iterator] create a new current = 0 on every call?
Correct! Each call to [Symbol.iterator] creates a new current = 0 closed over by that iterator's own next() method, which is what lets a collection hand out several independent iterators, each tracking its own position.
Not quite. Each call to [Symbol.iterator] creates a new current = 0 closed over by that iterator's own next() method, which is what lets a collection hand out several independent iterators, each tracking its own position.
Question 2 of 10
When does wrapping a collection in a full iterator abstraction add ceremony without real benefit?
Correct! For a plain array or list that's only ever looped over one way, wrapping it in a full iterator abstraction adds ceremony without real benefit.
Not quite. For a plain array or list that's only ever looped over one way, wrapping it in a full iterator abstraction adds ceremony without real benefit.
Question 3 of 10
Can a collection hand out several iterators at once?
Correct! A collection can hand out several independent iterators at once, each tracking its own position, so several passes over the same collection can run at once without stepping on each other.
Not quite. A collection can hand out several independent iterators at once, each tracking its own position, so several passes over the same collection can run at once without stepping on each other.
Question 4 of 10
What is the relationship between IterableCollection and ConcreteCollection in the structure?
Correct! An IterableCollection interface promises a way to produce a compatible iterator, and a ConcreteCollection implements that by handing back a fresh ConcreteIterator on request.
Not quite. An IterableCollection interface promises a way to produce a compatible iterator, and a ConcreteCollection implements that by handing back a fresh ConcreteIterator on request.
Question 5 of 10
What is a stated risk of mutating a collection while an iterator is traversing it?
Correct! An iterator can be invalidated or produce inconsistent results if the underlying collection is mutated while the traversal is still in progress.
Not quite. An iterator can be invalidated or produce inconsistent results if the underlying collection is mutated while the traversal is still in progress.
Question 6 of 10
What small interface does every iterator expose, regardless of what it walks?
Correct! Every iterator exposes the same small interface: something like "is there a next element" and "give me the next element", so client code never has to care about the underlying structure.
Not quite. Every iterator exposes the same small interface: something like "is there a next element" and "give me the next element", so client code never has to care about the underlying structure.
Question 7 of 10
In the media-library example, why does a shuffle button, an export feature, and a search filter each end up with a different loop shape?
Correct! Every part of the UI that needs to loop over one of these collections ends up with a different loop shape depending on which structure it's touching - a flat array, a hash set, or a ring buffer.
Not quite. Every part of the UI that needs to loop over one of these collections ends up with a different loop shape depending on which structure it's touching - a flat array, a hash set, or a ring buffer.
Question 8 of 10
What does Iterator provide, without revealing the collection's underlying storage?
Correct! Iterator provides a uniform way to step through the elements of a collection one at a time, without revealing whether it's stored as an array, tree, linked list, or something else.
Not quite. Iterator provides a uniform way to step through the elements of a collection one at a time, without revealing whether it's stored as an array, tree, linked list, or something else.
Question 9 of 10
What problem arises if traversal logic is written straight into each collection class?
Correct! That class now has two jobs: managing its data and knowing every way someone might want to walk through it, and switching the underlying storage later breaks every piece of client code that assumed array indexing.
Not quite. That class now has two jobs: managing its data and knowing every way someone might want to walk through it, and switching the underlying storage later breaks every piece of client code that assumed array indexing.
Question 10 of 10
Which of these is listed as a genuine benefit of Iterator?
Correct! Collection classes shrink back down to just managing storage, since every traversal algorithm they used to carry moves out into its own iterator class.
Not quite. Collection classes shrink back down to just managing storage, since every traversal algorithm they used to carry moves out into its own iterator class.