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...
🟢 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
- Frequent writes — each write copies the entire array (CopyOnWriteArrayList)
- Large lists — snapshot consumes O(n) memory
- 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
- Fail-fast → indicator of logic bugs
- Snapshot → for listeners, infrequent writes
- Weakly Consistent → high-concurrency
- modCount not volatile → best-effort
- removeIf > iterator.remove() for ArrayList
- 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
ConcurrentModificationExceptionwhen the collection is modified (ArrayList, HashMap) - Mechanism: comparing the collection’s
modCountwith the iterator’sexpectedModCount - 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
modCountis NOT volatile → in multi-threading, CME is not guaranteed (best-effort)iterator.remove()— the only safe way to remove during fail-fast iterationremoveIf()is better thaniterator.remove()for ArrayList — O(n) instead of O(n^2)
Frequent follow-up questions:
- Why doesn’t fail-fast guarantee CME in multi-threading? —
modCountis 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]]