What is Iterator Pattern?
4. Fail-Safe collections for concurrency 5. Don't modify collection during iteration
Junior Level
Iterator is a pattern for iterating over collection elements without knowing its internal structure.
Simple analogy: TV remote. You switch channels with a “Next” button without knowing how the TV internally switches channels.
Example:
// Iterator in Java — this is what forEach uses
List<String> list = List.of("A", "B", "C");
// Explicit Iterator
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String item = iterator.next();
System.out.println(item);
}
// Same thing via forEach (uses Iterator internally)
list.forEach(System.out::println);
Why needed:
- Unified iteration approach for any collection
- No need to know internal structure
- Can remove elements during iteration
Middle Level
Iterator vs ListIterator
List<String> list = new ArrayList<>(List.of("A", "B", "C"));
// List.of() returns an immutable list, add() throws UnsupportedOperationException
// Iterator — forward only
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String item = it.next();
it.remove(); // Can remove
}
// ListIterator — forward and backward
ListIterator<String> lit = list.listIterator();
while (lit.hasNext()) {
lit.next();
lit.add("new"); // Can add!
}
while (lit.hasPrevious()) {
lit.previous(); // Can go backward!
}
Custom Iterator
class Tree<T> {
private Node<T> root;
public Iterator<T> iterator() {
return new TreeIterator();
}
private class TreeIterator implements Iterator<T> {
private Queue<Node<T>> queue = new LinkedList<>();
TreeIterator() {
if (root != null) queue.add(root);
}
public boolean hasNext() {
return !queue.isEmpty();
}
public T next() {
Node<T> node = queue.poll();
if (node.left != null) queue.add(node.left);
if (node.right != null) queue.add(node.right);
return node.value;
}
}
}
Senior Level
Fail-Fast vs Fail-Safe
// Fail-Fast (ArrayList, HashMap)
List<String> list = new ArrayList<>(List.of("A", "B", "C"));
Iterator<String> it = list.iterator();
it.next();
list.add("D"); // Collection modified
it.next(); // -> ConcurrentModificationException!
// Fail-Safe (CopyOnWriteArrayList, ConcurrentHashMap)
List<String> safeList = new CopyOnWriteArrayList<>(List.of("A", "B", "C"));
Iterator<String> safeIt = safeList.iterator();
safeIt.next();
safeList.add("D"); // Collection modified
safeIt.next(); // Doesn't throw exception, but iterator only sees elements at creation time (snapshot). New element "D" is NOT visible in current iteration.
Stream API as Iterator
// Stream API uses Spliterator internally, but provides a higher-level API
// (lazy evaluation, parallel streams, functional composition).
list.stream()
.filter(s -> s.startsWith("A"))
.map(String::toUpperCase)
.forEach(System.out::println);
// Stream supports lazy evaluation
// Iterator — does not
Production Experience
Real scenario: Fail-Fast bug
- Removing element in forEach loop -> ConcurrentModificationException
- Solution: iterator.remove() or removeIf()
- Lesson: cannot modify collection during iteration
Best Practices
- iterator.remove() for removal during iteration
- removeIf() for filtering (Java 8+)
- Stream for complex operations
- Fail-Safe collections for concurrency
- Don’t modify collection during iteration
Senior Summary
- Iterator = universal iteration approach
- Fail-Fast = exception on modification
- Fail-Safe = iterator over a copy
- Stream API = uses Spliterator, provides lazy evaluation and functional composition
- removeIf() preferred over manual removal
Interview Cheat Sheet
Must know:
- Iterator — universal iteration approach hiding collection’s internal structure
- Fail-Fast (ArrayList, HashMap) — ConcurrentModificationException on modification during iteration
- Fail-Safe (CopyOnWriteArrayList, ConcurrentHashMap) — iterator over snapshot, doesn’t throw exception
- Stream API uses Spliterator — lazy evaluation, parallel streams, functional composition
- For removal during iteration: iterator.remove() or removeIf() (Java 8+)
- ListIterator extends Iterator: backward movement, element addition
- Cannot modify collection during iteration (except iterator.remove() and Fail-Safe collections)
Common follow-up questions:
- How to remove an element during iteration? — iterator.remove() or list.removeIf(predicate)
- How does Fail-Fast differ from Fail-Safe? — Fail-Fast throws exception, Fail-Safe works on snapshot
- Why can’t forEach remove elements? — forEach uses Iterator internally, modification -> ConcurrentModificationException
- What is Spliterator? — Parallel-capable Iterator for Stream API, supports partitioning
Red flags (DO NOT say):
- “I modify collections in forEach loops” — ConcurrentModificationException guaranteed
- “Fail-Safe means data is always current” — iterator sees snapshot, new elements are NOT visible
- “Iterator is obsolete with Stream API” — Iterator is used under the hood of Stream and forEach
- “removeIf() is the same as iterator.remove()” — removeIf() takes a Predicate, higher-level
Related topics:
- [[11. How is Observer implemented in Java]] — iterating over listeners
- [[02. What pattern categories exist]] — Behavioral patterns
- [[10. When to use Strategy]] — Stream API as an alternative
- [[16. What anti-patterns do you know]] — God Object with collections
- [[01. What are design patterns]] — Iterator in Modern Java