How do fail-fast iterators in the Java Collections Framework work, and how do they differ from fail-safe iterators?
answer
- modCount vs expectedModCount → CME on mismatch
- Fail-fast = best-effort, not a guarantee (bug detection only)
- CME often happens single-threaded (remove in for-each)
- Use iterator.remove() / removeIf to mutate safely
- Fail-safe: CopyOnWriteArrayList (snapshot), ConcurrentHashMap (weakly consistent)
basics
~20 sA fail-fast iterator (like ArrayList's) throws ConcurrentModificationException if the collection is changed while you iterate it, so you find the bug quickly. A fail-safe iterator (like CopyOnWriteArrayList's) works on a snapshot and never throws, but may not see the latest changes.
solid answer
~40 sMost standard collections (ArrayList, HashMap, HashSet) return **fail-fast** iterators. They keep an internal `modCount` (modification counter) and remember its value when the iterator is created; on each `next()` they compare, and if the collection was structurally modified by anything other than the iterator's own `remove()`, they throw `ConcurrentModificationException`. This is **best-effort** detection meant to catch bugs early, not a guarantee — you must not rely on it for correctness. **Fail-safe** (more precisely, weakly consistent) iterators, used by concurrent collections like CopyOnWriteArrayList and ConcurrentHashMap, iterate over a snapshot or tolerate concurrent changes, so they never throw CME but may not reflect modifications made after the iterator was created. Practical guidance: remove during iteration via the iterator's own `remove()` (or `removeIf`); for concurrent access, use a concurrent collection rather than relying on either behavior.
code
java · 19 linesList<String> list = new ArrayList<>(List.of("a", "b", "c"));
// WRONG: throws ConcurrentModificationException (single-threaded!)
for (String s : list) {
if (s.equals("b")) {
list.remove(s); // structural modification not via the iterator
}
}
// RIGHT: use the iterator's own remove()
Iterator<String> it = list.iterator();
while (it.hasNext()) {
if (it.next().equals("b")) {
it.remove(); // updates expectedModCount, no CME
}
}
// RIGHT (concise): removeIf
list.removeIf(s -> s.equals("b"));go deeper
Knows that modifying a list while looping over it can throw ConcurrentModificationException, and that you should use the iterator's remove() or removeIf instead.
Explains the modCount/expectedModCount mechanism, that it is best-effort, that CME is common in single-threaded code, and names fail-safe alternatives like CopyOnWriteArrayList.
Contrasts snapshot (CopyOnWrite) vs weakly consistent (ConcurrentHashMap), the precise terminology, the cost/staleness trade-offs, and when to choose each for concurrent access.
Guides collection choice across a system under concurrency, weighing copy-on-write cost, weak consistency semantics, and the reliability of bug-detection vs correctness guarantees in API contracts.
## Setup: iterating a collection An **iterator** is an object that walks a collection one element at a time (`hasNext()` / `next()`). The question is what happens if the **collection is modified while you are iterating it** — for example you add or remove an element inside a `for (X x : list)` loop (which uses an iterator under the hood). There are two design philosophies, and the names map to the fail-fast theme of this topic. ## Fail-fast iterators (the default in java.util) Collections like `ArrayList`, `HashMap`, `HashSet`, `LinkedList` return **fail-fast** iterators. Their goal is to **detect a bug — concurrent structural modification — as early as possible and throw**, rather than silently returning wrong or skipped elements. **Mechanism — `modCount`:** the collection keeps an `int modCount` that it increments on every *structural* modification (add/remove that changes the size/structure). When you create an iterator, it copies that value into `expectedModCount`. On each `next()` (and often `remove()`), it calls a `checkForComodification()` that compares `modCount` to `expectedModCount`; if they differ, it throws **`ConcurrentModificationException` (CME)**. **Crucial caveat — best-effort, not guaranteed:** the Javadoc is explicit that fail-fast behavior **cannot be guaranteed** and must be used **only to detect bugs**. The `modCount` check is unsynchronized, so in concurrent scenarios it may miss a modification or, conversely, the timing may vary. *Despite the name, 'ConcurrentModificationException' often occurs in single-threaded code* — e.g. removing from a list inside an enhanced-for loop — because that is also a structural modification the iterator didn't make. **Doing it right:** to remove while iterating, use the **iterator's own `remove()`** (which updates `expectedModCount` so no CME is thrown), or `Collection.removeIf(...)`. Do *not* call `list.remove(x)` inside a for-each. ## Fail-safe / weakly consistent iterators (concurrent collections) Concurrent collections take the opposite approach so that iteration can coexist with concurrent writers: - **`CopyOnWriteArrayList` / `CopyOnWriteArraySet`**: the iterator is over a **snapshot** of the array taken at creation time. Writers create a fresh copy, so the iterator never sees later changes and **never throws CME**. The trade-off is memory/copy cost and staleness. - **`ConcurrentHashMap`**: its iterators are **weakly consistent** — they never throw CME, traverse elements as they existed at or since iterator creation, and *may or may not* reflect modifications made after creation. They also tolerate concurrent writers without locking the whole map. "Fail-safe" is the common interview term; the JDK Javadoc prefers **"weakly consistent"** because these iterators don't *guarantee* a perfect snapshot for ConcurrentHashMap (CopyOnWrite ones truly are snapshots). Saying 'weakly consistent' signals precision. ## The trade-off, summarized | | Fail-fast (ArrayList, HashMap) | Fail-safe / weakly consistent (CopyOnWriteArrayList, ConcurrentHashMap) | |---|---|---| | On concurrent modification | throws CME (best-effort) | never throws | | Sees later modifications | n/a (it bailed) | maybe not (snapshot / weak) | | Cost | cheap | copy/memory or relaxed consistency | | Intent | catch bugs early | allow safe concurrent iteration | ## Why it belongs to 'fail-fast' as a principle Fail-fast iterators are a concrete library example of the broader principle from this topic: rather than letting a likely-bug (mutating a structure mid-iteration) silently produce a corrupt or unpredictable result, the library **throws immediately and loudly** so you find and fix the real mistake.
- Why can ConcurrentModificationException occur in a single-threaded program?Because it detects any structural modification not made through the iterator — classically, calling list.remove(x) inside a for-each loop. The 'Concurrent' in the name is misleading; it really means 'modified out from under the iterator'. Fix it with iterator.remove() or removeIf.
- Why does the Javadoc prefer 'weakly consistent' over 'fail-safe' for ConcurrentHashMap?Because its iterator doesn't promise a perfect point-in-time snapshot — it may or may not reflect modifications made after creation, and never throws. 'Fail-safe' overstates the guarantee; 'weakly consistent' precisely describes the behavior. CopyOnWriteArrayList is a true snapshot.
saying these in an interview costs you the question
- Claiming fail-fast iterators guarantee CME on every concurrent modification — the Javadoc says it is best-effort and for bug detection only.
- Thinking CME only happens with multiple threads — the common cause is removing inside a single-threaded for-each.
- Believing fail-safe iterators always reflect the latest data — they iterate a snapshot or are weakly consistent and may miss recent changes.
- Calling collection.remove(x) inside an enhanced-for loop instead of iterator.remove()/removeIf.