library
intermediateFail-Fast vs Fail-Safe Iterators
Understand why ArrayList throws ConcurrentModificationException but ConcurrentHashMap doesn't.
Two different answers to "the collection changed while I was iterating".
Fail-fast = a security guard who immediately stops you if someone else enters the room during your presentation. Fail-safe = a camera recording — keeps going regardless of who enters, but the recording might miss late arrivals.
Key Concepts
1
fail-fast ArrayList, HashMap, HashSet
throws ConcurrentModificationException
fail-safe ConcurrentHashMap, CopyOnWriteArrayList
never throws; may not see recent changes2
How fail-fast works. The collection keeps a modCount, bumped on every structural change. The iterator
records it at creation and checks it on every next():
modCountnext()
3
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}4
Which is why this fails on the second iteration, not the removal:
for (String s : list)
if (s.isEmpty()) list.remove(s);5
The remove succeeds. The next next() notices the count moved and throws — so the stack trace points at
the loop, not at the line that caused it.
removenext()
6
And why removing the second-to-last element appears to work. After the removal, hasNext() compares the
cursor against the new smaller size, returns false, and the loop exits before any check runs. The same code
then throws for any other element, which makes it look intermittent.
hasNext()
7
CopyOnWriteArrayList iterates a snapshot.
var list = new CopyOnWriteArrayList<>(List.of("a", "b"));
for (String s : list) list.add("c"); // terminates, no exceptionCopyOnWriteArrayList
8
The iterator holds the array as it was when created, so writes it cannot see. No exception, and no view of
the additions either.
9
ConcurrentHashMap is weakly consistent. Its iterator reflects the map at some point during traversal:
every entry present at the start appears, and entries added during it may or may not. It never throws.
ConcurrentHashMap
10
Fail-fast is a debugging aid, not a guarantee. The modCount check has no synchronisation, so a change
from another thread may go unseen. Do not catch the exception; use a concurrent collection or hold a lock.
modCount
11
The fixes, in order of preference.
list.removeIf(pred) clearest
iterator.remove() when you need more control
collect, then modify after the loop
a concurrent collection when threads really are sharing it12
What the interviewer is probing.1. "What makes an iterator fail-fast?" Probing: the mechanism, not just the label. Stalls:
"It checks for changes." Moves up: the collection keeps a modCount, the iterator records it and
compares on every next(), throwing ConcurrentModificationException when it differs.
13
2. "Can you rely on catching ConcurrentModificationException?" Probing: whether you know it is
best effort. Stalls: "Yes, catch and retry." Moves up: no — the check has no synchronisation, so
a change from another thread may go unseen; it is a debugging aid, not a control mechanism.
14
3. "How does CopyOnWriteArrayList avoid it?" Probing: the snapshot. Stalls: "It locks."
Moves up: its iterator holds the array as it was at creation, so writes during the loop are simply
invisible to it — and iterator.remove is unsupported.
15
4. "What does 'weakly consistent' mean for ConcurrentHashMap?" Probing: precision about the
guarantee. Stalls: "It might be wrong." Moves up: every entry present at the start is seen,
entries added during traversal may or may not be, and it never throws.