Stage 5: Collections compared, lesson 8 of 8

Fail-fast vs fail-safe iterators

Intermediate3 min read@since 8Code runs on your Java 25
Explain it forThe essentials plus production detail and pitfalls.

What happens when a collection changes while you're iterating over it?

  • Fail-fast iterators (ArrayList, HashMap, HashSet, LinkedList…) throw ConcurrentModificationException as soon as they notice the collection was structurally modified other than through the iterator itself. Each collection keeps a modification counter (modCount); the iterator compares it with the value it expected.
  • Fail-safe iterators (the Javadoc calls them snapshot or weakly consistent) never throw:
  • CopyOnWriteArrayList iterates over a snapshot of the array taken when iteration began, so it never sees later changes.
  • ConcurrentHashMap and ConcurrentLinkedQueue iterators are weakly consistent: they reflect some changes made during iteration, and never fail.

Fail-fast behaviour is a bug detector, and the most common trigger is a single thread removing items inside a for-each loop. The fix is iterator.remove() or removeIf.

Side by side

Fail-fastFail-safe (snapshot / weakly consistent)
Throws ConcurrentModificationExceptionYesNever
How it worksChecks modCount on each stepIterates a snapshot, or tolerates concurrent changes
Sees changes made during iterationNo: it throwsSnapshot: never; weakly consistent: maybe
ExamplesArrayList, HashMap, HashSet, LinkedListCopyOnWriteArrayList, ConcurrentHashMap, ConcurrentLinkedQueue
Thread-safeNoYes
Extra costNoneCopy-on-write copies the whole array on every write
PurposeDetect bugs earlyAllow changes while others iterate

Example

Java
List<String> names = new ArrayList<>(List.of("Asha", "Ravi", "Meera"));

for (String n : names) {
    if (n.startsWith("R")) names.remove(n);      // ConcurrentModificationException on the next step
}
names.removeIf(n -> n.startsWith("R"));          // the fix

List<String> listeners = new CopyOnWriteArrayList<>(List.of("email", "sms"));
for (String l : listeners) {
    listeners.add("push");                       // no exception: the loop sees the old snapshot
}
System.out.println(listeners);                   // [email, sms, push, push]

Common mistake

Removing items inside a for-each loop over an ArrayList. It throws ConcurrentModificationException even with a single thread.

Under the hood

Fail-fast checking is "best effort": modCount isn't volatile, so with several threads the exception may never come; never use it as a thread-safety mechanism. "Fail-safe" is an interview term, not a Javadoc term; say "snapshot" for copy-on-write collections and "weakly consistent" for concurrent ones. Copy-on-write collections suit read-mostly data such as listener lists and configuration, not frequently updated lists.

Check yourself

You add to a CopyOnWriteArrayList inside a for-each loop over it. What happens?

How this connects

Where this leads

You've reached the end of this thread. Try a learning path for what's next.

Part of Multithreading: beginner to advanced, Java 8 and collections, practically.

Was this lesson helpful?

Finished reading? Mark it complete to track your progress.