You have a playlist. Sometimes it’s stored as an array. Sometimes as a linked list. Sometimes in a database. The UI code shouldn’t care. It just needs to play songs one by one.
Iterator lets you traverse any collection without knowing how it’s stored.
What is the Iterator Pattern?
Iterator provides a way to access collection elements sequentially without exposing the underlying representation. The iterator object tracks position and provides consistent access methods.

MyList creates a ListIter. Clients use the iterator to traverse, never accessing the collection’s internals directly.
When to Use Iterator
| Use Iterator When | Skip Iterator When |
|---|---|
| You want uniform traversal across different collections | Simple array iteration suffices |
| You need to hide internal representation | Structure is already public |
| You want multiple simultaneous traversals | Only one traversal at a time |
| Traversal logic is complex | Standard for-each works |
In Java, you rarely implement Iterator from scratch because java.util.Iterator and Iterable are built-in. But understanding the pattern helps when creating custom collections.
Implementation
Custom Tree Iterator
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
public class TreeNode<T> {
private final T value;
private final List<TreeNode<T>> children;
public TreeNode(T value) {
this.value = value;
this.children = new ArrayList<>();
}
public void addChild(TreeNode<T> child) {
children.add(child);
}
public T getValue() { return value; }
public List<TreeNode<T>> getChildren() { return children; }
}
public class Tree<T> implements Iterable<T> {
private TreeNode<T> root;
public Tree(TreeNode<T> root) {
this.root = root;
}
@Override
public Iterator<T> iterator() {
return new BreadthFirstIterator<>(root);
}
public Iterator<T> depthFirstIterator() {
return new DepthFirstIterator<>(root);
}
}
public class BreadthFirstIterator<T> implements Iterator<T> {
private final Queue<TreeNode<T>> queue = new LinkedList<>();
public BreadthFirstIterator(TreeNode<T> root) {
if (root != null) {
queue.add(root);
}
}
@Override
public boolean hasNext() {
return !queue.isEmpty();
}
@Override
public T next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
TreeNode<T> node = queue.poll();
queue.addAll(node.getChildren());
return node.getValue();
}
}
public class DepthFirstIterator<T> implements Iterator<T> {
private final Deque<TreeNode<T>> stack = new ArrayDeque<>();
public DepthFirstIterator(TreeNode<T> root) {
if (root != null) {
stack.push(root);
}
}
@Override
public boolean hasNext() {
return !stack.isEmpty();
}
@Override
public T next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
TreeNode<T> node = stack.pop();
// Push children in reverse order for left-to-right traversal
List<TreeNode<T>> children = node.getChildren();
for (int i = children.size() - 1; i >= 0; i--) {
stack.push(children.get(i));
}
return node.getValue();
}
}
Usage
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// Build a tree
TreeNode<String> root = new TreeNode<>("CEO");
TreeNode<String> cto = new TreeNode<>("CTO");
TreeNode<String> cfo = new TreeNode<>("CFO");
root.addChild(cto);
root.addChild(cfo);
cto.addChild(new TreeNode<>("Dev Lead"));
cto.addChild(new TreeNode<>("QA Lead"));
cfo.addChild(new TreeNode<>("Accountant"));
Tree<String> orgChart = new Tree<>(root);
// Breadth-first (level by level)
System.out.println("Breadth-first:");
for (String name : orgChart) {
System.out.println(" " + name);
}
// CEO, CTO, CFO, Dev Lead, QA Lead, Accountant
// Depth-first
System.out.println("Depth-first:");
Iterator<String> dfs = orgChart.depthFirstIterator();
while (dfs.hasNext()) {
System.out.println(" " + dfs.next());
}
// CEO, CTO, Dev Lead, QA Lead, CFO, Accountant
Filtered Iterator
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
public class FilteredIterator<T> implements Iterator<T> {
private final Iterator<T> source;
private final Predicate<T> filter;
private T nextElement;
private boolean hasNext;
public FilteredIterator(Iterator<T> source, Predicate<T> filter) {
this.source = source;
this.filter = filter;
advance();
}
private void advance() {
hasNext = false;
while (source.hasNext()) {
T candidate = source.next();
if (filter.test(candidate)) {
nextElement = candidate;
hasNext = true;
return;
}
}
}
@Override
public boolean hasNext() {
return hasNext;
}
@Override
public T next() {
if (!hasNext) {
throw new NoSuchElementException();
}
T result = nextElement;
advance();
return result;
}
}
// Usage
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Iterator<Integer> evenNumbers = new FilteredIterator<>(
numbers.iterator(),
n -> n % 2 == 0
);
while (evenNumbers.hasNext()) {
System.out.println(evenNumbers.next()); // 2, 4, 6, 8, 10
}
Paginated Iterator
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class PaginatedIterator<T> implements Iterator<List<T>> {
private final Iterator<T> source;
private final int pageSize;
public PaginatedIterator(Iterator<T> source, int pageSize) {
this.source = source;
this.pageSize = pageSize;
}
@Override
public boolean hasNext() {
return source.hasNext();
}
@Override
public List<T> next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
List<T> page = new ArrayList<>(pageSize);
while (source.hasNext() && page.size() < pageSize) {
page.add(source.next());
}
return page;
}
}
// Usage - process in batches
List<User> users = userRepository.findAll();
Iterator<List<User>> pages = new PaginatedIterator<>(users.iterator(), 100);
while (pages.hasNext()) {
List<User> batch = pages.next();
processUserBatch(batch);
}
Java’s Iterable and Iterator
Java’s standard interfaces:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class Playlist implements Iterable<Song> {
private final List<Song> songs = new ArrayList<>();
public void addSong(Song song) {
songs.add(song);
}
@Override
public Iterator<Song> iterator() {
return songs.iterator(); // Delegate to internal list
}
// Custom iterator for shuffle mode
public Iterator<Song> shuffleIterator() {
List<Song> shuffled = new ArrayList<>(songs);
Collections.shuffle(shuffled);
return shuffled.iterator();
}
// Reverse iterator
public Iterator<Song> reverseIterator() {
return new Iterator<Song>() {
private int index = songs.size() - 1;
@Override
public boolean hasNext() {
return index >= 0;
}
@Override
public Song next() {
if (!hasNext()) throw new NoSuchElementException();
return songs.get(index--);
}
};
}
}
// Usage with for-each (uses iterator() automatically)
Playlist playlist = new Playlist();
playlist.addSong(new Song("Song A"));
playlist.addSong(new Song("Song B"));
for (Song song : playlist) {
song.play();
}
How It Works
sequenceDiagram
participant Client
participant Collection
participant Iterator
Client->>Collection: iterator()
Collection->>Iterator: Create new iterator
Collection-->>Client: Iterator instance
loop While hasNext()
Client->>Iterator: hasNext()
Iterator-->>Client: true
Client->>Iterator: next()
Iterator-->>Client: Element
end
Client->>Iterator: hasNext()
Iterator-->>Client: false
Common Mistakes
1. Modifying Collection During Iteration
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Wrong - ConcurrentModificationException
for (String item : list) {
if (item.equals("remove me")) {
list.remove(item); // Throws exception!
}
}
// Right - use Iterator.remove()
Iterator<String> iter = list.iterator();
while (iter.hasNext()) {
if (iter.next().equals("remove me")) {
iter.remove(); // Safe removal
}
}
// Or use removeIf
list.removeIf(item -> item.equals("remove me"));
2. Calling next() Without hasNext()
1
2
3
4
5
6
7
// Wrong - may throw NoSuchElementException
String first = iterator.next(); // What if empty?
// Right - always check
if (iterator.hasNext()) {
String first = iterator.next();
}
3. Reusing Exhausted Iterator
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Wrong - iterator is exhausted
while (iter.hasNext()) {
process(iter.next());
}
// iter.hasNext() is now always false
while (iter.hasNext()) { // Never enters - iter is done!
process(iter.next());
}
// Right - create new iterator
for (String item : collection) {
process(item);
}
for (String item : collection) { // Works - new iterator each time
process(item);
}
Real-World Examples
Java Collections: Every List, Set, Map implements Iterable.
JDBC ResultSet: next() moves to next row, hiding how data is fetched.
Stream API: Streams use internal iteration with lazy evaluation.
File Systems: DirectoryStream iterates over directory contents.
Related Patterns
Composite structures are often traversed using iterators.
Visitor can work with iterators to apply operations during traversal.
Factory Method is used by collections to create appropriate iterators.
Wrapping Up
Iterator provides uniform access to collections regardless of their internal structure. It encapsulates traversal logic and enables multiple simultaneous iterations.
In Java, implement Iterable to enable for-each loops. Use custom iterators for special traversal needs like filtering, pagination, or tree walking.
The pattern hides complexity. Clients don’t care if it’s an array, linked list, tree, or database. They just call hasNext() and next().
Further Reading: