Explain the difference between fail-fast and fail-safe (weakly consistent) iterators in Java, with examples.
answer
- fail-fast = java.util, throws CME via modCount
- fail-safe/weakly-consistent = java.util.concurrent
- CopyOnWriteArrayList = snapshot, no CME, write O(n)
- ConcurrentHashMap = weakly consistent, never throws
- CME is best-effort, not thread safety
basics
~10 sFail-fast iterators (like ArrayList's or HashMap's) throw ConcurrentModificationException if the collection changes during iteration. Fail-safe iterators (like CopyOnWriteArrayList's or ConcurrentHashMap's) iterate over a snapshot or tolerate changes without throwing.
solid answer
~40 sFail-fast iterators, used by the non-concurrent java.util collections, detect structural modification during iteration via a modCount check and throw ConcurrentModificationException immediately. It's a best-effort debugging aid, not synchronized, so you can't rely on it for thread safety. Fail-safe — more precisely, weakly consistent — iterators come from the java.util.concurrent collections. CopyOnWriteArrayList iterates over an immutable snapshot of the array taken at iterator creation, so concurrent writes never affect it and never throw, but you won't see later changes and the snapshot costs memory. ConcurrentHashMap's iterators are weakly consistent: they never throw CME, traverse elements existing at or after creation, and may or may not reflect modifications made after the iterator started. The trade-off is consistency and freshness versus the ability to mutate safely under concurrency.
go deeper
Know that ordinary collections throw ConcurrentModificationException when modified during iteration, while concurrent collections don't.
Explain fail-fast via modCount and name fail-safe examples (CopyOnWriteArrayList, ConcurrentHashMap), with the basic trade-off.
Distinguish snapshot (COW) from weakly consistent (CHM) precisely, state the guarantees of each, the unsupported iterator ops on COW, and why CME is best-effort.
Choose the right collection for a concurrency/throughput profile (read-mostly vs write-heavy), reason about memory/GC cost of copy-on-write, consistency semantics for callers, and when external locking beats either.
## Two failure philosophies When a collection is modified *while* you are iterating it, an iterator must decide what to do. Java's collections split into two camps. ### Fail-fast Used by the ordinary, non-thread-safe collections in **`java.util`** — `ArrayList`, `LinkedList`, `HashMap`, `HashSet`, `TreeMap`, etc. Each such collection keeps a **`modCount`** field, incremented on every *structural modification* (an add/remove that changes size). When you create an iterator, it copies `modCount` into `expectedModCount`. On every `next()` it calls `checkForComodification()`: if `modCount != expectedModCount`, it throws **`ConcurrentModificationException`** (CME). The philosophy: *fail loudly and early* rather than silently produce wrong results (skipped/duplicated elements, reading a half-resized array). Crucially: - It is **best-effort** — the check is *not* synchronized. In a true multithreaded race it may miss the modification or throw spuriously. **Never use CME for program logic or as a concurrency control.** - It also fires for *single-threaded* misuse, e.g. modifying a list directly inside a for-each loop. That is its most common real-world trigger and its most useful role (catching bugs). - The fix for legitimate single-threaded edits is the iterator's own `remove()`/`add()`/`set()`, which keep `expectedModCount` in sync. ### Fail-safe / weakly consistent Used by the concurrent collections in **`java.util.concurrent`**. "Fail-safe" is a popular term, but the Javadoc's precise terms are **snapshot** and **weakly consistent**. - **`CopyOnWriteArrayList` / `CopyOnWriteArraySet`**: every mutation copies the whole backing array; the iterator holds a reference to the array *as it was when the iterator was created* — an immutable **snapshot**. Therefore: it never throws CME, it never reflects modifications made after creation, and its own `remove()/set()/add()` are unsupported (throw `UnsupportedOperationException`). Great for read-mostly, write-rarely data (e.g. listener lists); costly for write-heavy use because each write is O(n). - **`ConcurrentHashMap`, `ConcurrentLinkedQueue`, `ConcurrentSkipListMap`, etc.**: their iterators are **weakly consistent**. Guarantees: (1) they never throw CME; (2) they traverse elements as they existed at iterator construction and *may* reflect later modifications, but no guarantee either way; (3) an element is returned at most once. They don't snapshot the whole structure, so they're cheaper than copy-on-write and stay close to live state, at the cost of not giving you a single consistent point-in-time view. ## Side-by-side | Aspect | Fail-fast (java.util) | Snapshot/weakly-consistent (java.util.concurrent) | |---|---|---| | On concurrent modification | throws CME (best-effort) | never throws | | View of data | live | snapshot (COW) or weakly consistent (CHM) | | Iterator.remove() | supported (the safe edit path) | COW: unsupported; CHM: supported, weakly consistent | | Cost | cheap | COW: O(n) per write, memory; CHM: modest | | Use when | single-threaded; bug detection | concurrent read/write without external locking | ## Example ```java // Fail-fast: throws ConcurrentModificationException List<Integer> a = new ArrayList<>(List.of(1,2,3)); for (Integer x : a) { a.add(x); } // CME // Snapshot: safe, but the new element is NOT seen by this iterator List<Integer> b = new CopyOnWriteArrayList<>(List.of(1,2,3)); for (Integer x : b) { b.add(x); } // no exception; loop ends after original 3 ``` ## Term recap - **Structural modification**: size-changing add/remove (not `set` of an existing slot). - **modCount / expectedModCount**: counters whose mismatch triggers CME. - **Best-effort**: detection that may miss/over-trigger under races; not a guarantee. - **Snapshot iterator**: iterates a frozen copy taken at creation (copy-on-write). - **Weakly consistent**: never throws, reflects some-but-not-all later changes, each element seen at most once.
- Does CopyOnWriteArrayList's iterator support remove()?No. Its iterator is over an immutable snapshot, so remove(), set(), and add() throw UnsupportedOperationException; you mutate the list directly instead.
- Will a ConcurrentHashMap iterator reflect entries added after it was created?It may or may not — weakly consistent iterators are allowed to reflect later modifications but are not guaranteed to; they never throw CME and return each element at most once.
saying these in an interview costs you the question
- Treating 'fail-safe' as a strict guarantee of a consistent view (it's snapshot or weakly consistent)
- Saying fail-fast guarantees thread safety
- Thinking ConcurrentHashMap's iterator snapshots the whole map like COW does
- Believing CopyOnWriteArrayList's iterator supports remove() (it doesn't)