Question 15 · Section 2

What is Iterator Pattern?

4. Fail-Safe collections for concurrency 5. Don't modify collection during iteration

Language versions: English Russian Ukrainian

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

  1. iterator.remove() for removal during iteration
  2. removeIf() for filtering (Java 8+)
  3. Stream for complex operations
  4. Fail-Safe collections for concurrency
  5. 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