Question 26 · Section 4

What are fail-fast and fail-safe iterators?

Important: fail-fast catches modifications made in any way except through the iterator's own methods. If you call iterator.remove(), the iterator updates its expectedModCount an...

Language versions: English Russian Ukrainian

🟢 Junior Level

Fail-fast — the iterator immediately throws an exception if the collection is modified. Fail-safe — the iterator continues to work, ignoring modifications.

// Fail-fast (ArrayList, HashMap)
List<String> list = new ArrayList<>(List.of("A", "B"));
for (String s : list) {
    list.add("C");  // → ConcurrentModificationException!
}

// Fail-safe (CopyOnWriteArrayList, ConcurrentHashMap)
List<String> safe = new CopyOnWriteArrayList<>(List.of("A", "B"));
for (String s : safe) {
    safe.add("C");  // → OK (iterator doesn't see "C")
}

🟡 Middle Level

Fail-fast: modCount mechanism

// Collection stores modCount (number of modifications)
// Iterator copies expectedModCount upon creation

// On each next():
if (modCount != expectedModCount) {
    throw new ConcurrentModificationException();
}

Important: fail-fast catches modifications made in any way except through the iterator’s own methods. If you call iterator.remove(), the iterator updates its expectedModCount and CME will not occur. This is a bug trap for catching errors in a single thread, not a synchronization mechanism.

Types of “fail-safe”

Snapshot-based:

// CopyOnWriteArrayList
Iterator<String> it = list.iterator();  // Snapshot of the array
list.add("X");  // New array
it.next();  // → From the old array (doesn't see "X")

Weakly Consistent:

// ConcurrentHashMap
Iterator<String> it = map.keySet().iterator();
map.put("X", 1);  // Live data
it.next();  // → MAY see "X" (but is not required to!)
// → Generally does not throw CME (for ConcurrentHashMap — guaranteed no)

Comparison

  Fail-fast Snapshot Weakly Consistent
Exception Yes No No
Copy No Yes No
Freshness Strict Stale Approximate

🔴 Senior Level

When NOT to use snapshot iterators

  1. Frequent writes — each write copies the entire array (CopyOnWriteArrayList)
  2. Large lists — snapshot consumes O(n) memory
  3. Data freshness required — snapshot becomes stale instantly after creation

modCount is not volatile!

// In a multi-threaded environment:
// Thread 1 modifies → modCount++
// Thread 2 may NOT see it (cache coherence)!
// → CME is NOT guaranteed in multi-threading!

// This is best-effort, not 100% protection

volatile — a keyword that guarantees visibility of changes between threads. Without volatile, one thread may write modCount++, and another thread (from its CPU cache) may see the old value.

Best-effort — the mechanism tries to catch the error but does not guarantee it in multi-threading. Fail-fast is a bug detector, not a synchronization mechanism.

removeIf optimization

// ❌ O(n²) for ArrayList
for (Iterator<String> it = list.iterator(); it.hasNext(); ) {
    if (condition(it.next())) {
        it.remove();  // System.arraycopy each time!
    }
}

// ✅ O(n) single pass
list.removeIf(e -> condition(e));
// → First finds all elements to remove
// → Then ONE array shift

Snapshot memory cost

CopyOnWriteArrayList: 1 million elements
  → Iterator = reference to array (4 MB)
  → add() = new array (4 MB)
  → Temporarily: 8 MB!

Best Practices

  1. Fail-fast → indicator of logic bugs
  2. Snapshot → for listeners, infrequent writes
  3. Weakly Consistent → high-concurrency
  4. modCount not volatile → best-effort
  5. removeIf > iterator.remove() for ArrayList
  6. iterator.remove() → the only safe way in fail-fast

Summary for Senior

  • Fail-fast = modCount check, best-effort
  • Snapshot = array copy, isolation
  • Weakly Consistent = live data, no Exception
  • modCount not volatile → not 100% in multi-threading
  • removeIf = O(n) optimization

🎯 Interview Cheat Sheet

Must know:

  • Fail-fast iterators throw ConcurrentModificationException when the collection is modified (ArrayList, HashMap)
  • Mechanism: comparing the collection’s modCount with the iterator’s expectedModCount
  • Fail-fast is a single-threaded bug detector, not a synchronization mechanism
  • Snapshot iterators (CopyOnWriteArrayList) — array copy at creation time, do not see subsequent changes
  • Weakly Consistent (ConcurrentHashMap) — live data, no CME, but do not guarantee freshness
  • modCount is NOT volatile → in multi-threading, CME is not guaranteed (best-effort)
  • iterator.remove() — the only safe way to remove during fail-fast iteration
  • removeIf() is better than iterator.remove() for ArrayList — O(n) instead of O(n^2)

Frequent follow-up questions:

  • Why doesn’t fail-fast guarantee CME in multi-threading?modCount is not volatile; a thread may not see the change due to cache coherence.
  • What does a CopyOnWriteArrayList iterator see after add()? — Old data (snapshot); new elements are not visible.
  • How does Snapshot differ from Weakly Consistent? — Snapshot — a copy (stale data), Weakly Consistent — live data (may see changes).
  • What is the snapshot cost for 1 million elements? — O(n) memory; array copy ~4 MB, on add() — temporarily 8 MB.

Red flags (DO NOT say):

  • “Fail-fast guarantees detection of changes in multi-threading” — no, modCount is not volatile, best-effort
  • “Snapshot iterator sees new elements” — no, snapshot is a copy at creation time
  • “CopyOnWriteArrayList is suitable for frequent writes” — no, each write = copy of the entire array
  • “Fail-fast is a synchronization mechanism” — no, it’s a logic bug detector

Related topics:

  • [[27. What is ConcurrentModificationException]]
  • [[28. How to properly remove elements during iteration]]
  • [[21. When to use synchronized collections]]