How does a fail-fast iterator detect concurrent modification internally?
answer
- modCount on the collection
- expectedModCount on the iterator
- checkForComodification on each next()
- Iterator.remove re-syncs expectedModCount
- unsynchronized = best-effort only
basics
~20 sThe collection keeps a counter called modCount that goes up every time you add or remove an element. When you make an iterator, it remembers that number. On each next() it compares them; if they differ, the collection changed behind its back, so it throws.
solid answer
~40 sFail-fast iterators rely on a modification counter, modCount, stored on the backing collection (e.g. AbstractList, HashMap). Every structural change (add/remove/clear) increments modCount. When you obtain an iterator, it copies the current modCount into a field called expectedModCount. On each next() and remove(), the iterator calls checkForComodification(), which throws ConcurrentModificationException if modCount != expectedModCount. The iterator's own remove() updates expectedModCount after modifying, so it stays consistent — that's why iterator-based removal is safe. The check is intentionally cheap and unsynchronized: it's a best-effort bug detector, so it gives no guarantee under true multi-threaded access and may occasionally miss a race or even false-positive. It exists to surface the common single-threaded misuse immediately rather than letting iteration silently produce wrong results.
code
java · 21 lines// Sketch of how AbstractList's iterator works (simplified from the JDK):
private class Itr implements Iterator<E> {
int cursor = 0;
int expectedModCount = modCount; // snapshot at creation
public E next() {
checkForComodification(); // throws if modCount changed
E next = get(cursor++);
return next;
}
public void remove() {
AbstractList.this.remove(--cursor);
expectedModCount = modCount; // re-sync so we don't throw next time
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}go deeper
Knows there's a hidden counter that detects changes and that the iterator's own remove() is the safe way to delete during a loop.
Can name modCount/expectedModCount, describe checkForComodification on next(), and explain why Iterator.remove() re-syncs the counter and is therefore safe.
Understands the check is unsynchronized/best-effort, that fail-fast is documented as non-guaranteed for concurrency, and that views share the parent's modCount.
Reasons about the design intent (cheap bug detection vs correctness guarantee), the memory-visibility implications of an unsynchronized counter, and steers architecture toward concurrent collections for shared mutable state.
## Goal We want to understand the exact machinery that makes a **fail-fast iterator** throw `ConcurrentModificationException` (CME). 'Fail-fast' means: the moment it detects the collection changed underneath it, it stops and throws, rather than continuing and returning wrong data. ## The key field: `modCount` Most mutable JDK collections (via base classes like `AbstractList`, and inside `HashMap`, `ArrayList`, etc.) hold an `int` field named **`modCount`** — short for *modification count*. Its rule: > Every **structural modification** increments `modCount`. A **structural modification** is one that changes the number of elements: `add`, `remove`, `clear`, `addAll`, etc. Replacing a value in place (`set`, or `put` on an existing key) does **not** change size and does **not** bump `modCount`. ## The snapshot: `expectedModCount` When you create an iterator, its constructor copies the collection's current `modCount` into its own field, **`expectedModCount`**: ```java int expectedModCount = modCount; ``` This is the iterator's memory of 'the collection as it was when I started.' ## The check: `checkForComodification()` On every `next()` (and the iterator's own `remove()`), the iterator runs: ```java final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); } ``` If someone changed the collection structurally through the collection's own API (e.g. `list.remove(x)`), `modCount` advanced but `expectedModCount` did not — they differ — and the next `next()` throws. ## Why `Iterator.remove()` is safe The iterator's own `remove()` performs the structural change *and then* re-syncs its snapshot: ```java public void remove() { // ... do the actual removal, which bumps modCount ... expectedModCount = modCount; // re-sync, so the next check passes } ``` Because it updates `expectedModCount` to match, subsequent `next()` calls don't throw. This is precisely why the *only* sanctioned way to remove during iteration is the iterator's own `remove()` (or higher-level helpers like `removeIf`, which do this internally). ## Best-effort, not a guarantee The `modCount` read in `checkForComodification()` is **not synchronized** and `modCount` is not `volatile` in the general case. Therefore: - Under true multi-threaded modification, the detection is **best-effort**: it may catch the race, miss it, or in rare cases throw spuriously. The Javadoc explicitly says fail-fast behavior 'cannot be guaranteed' and must be used **only to detect bugs**, never for program correctness. - This is why you must never write `try { iterate } catch (ConcurrentModificationException e) { /* assume thread safety */ }`. ## Sub-list and view gotchas Views like `subList`, `keySet`, `entrySet` share the parent's `modCount`. Structurally modifying the parent while iterating a view (or vice versa) also trips the check — another reason to treat CME as 'something structurally changed unexpectedly.' ## Contrast: weakly consistent iterators Concurrent collections (`ConcurrentHashMap`, `ConcurrentLinkedQueue`) deliberately **do not** use this modCount check. Their 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. `CopyOnWriteArrayList` goes further: its iterator walks an immutable **snapshot** taken at creation, so later writes are invisible to it and it never throws.
- Why does the JDK warn against catching ConcurrentModificationException to handle threading?Because the modCount check is unsynchronized and best-effort — it can miss races or fire spuriously. It's a debugging aid for detecting bugs, not a reliable signal of concurrent access. For real concurrency, use thread-safe/concurrent collections instead.
- If I call list.set(i, v) during iteration, does the iterator throw?No. set() doesn't change size, so it doesn't increment modCount, so checkForComodification() still passes. Only structural changes (size changes) are detected.
saying these in an interview costs you the question
- Saying the check compares size() or element hashes rather than modCount vs expectedModCount.
- Believing modCount detection is a reliable concurrency/thread-safety guarantee — it's unsynchronized and best-effort.
- Thinking value updates (set/put on existing key) bump modCount — only structural changes do.
- Not knowing why Iterator.remove() is safe (it re-syncs expectedModCount).