What is the difference between fail-fast and fail-safe it… — Cracked Java
// Java Collections Framework · List, ArrayList, LinkedList
MidTheoryGoogle

What is the difference between fail-fast and fail-safe iterators?

Fail-fast iterators throw ConcurrentModificationException the moment they detect the underlying collection was structurally modified after the iterator was created. Fail-safe iterators iterate over a snapshot (or a tolerant view) of the collection — they never throw, but they may not see the most recent state. Most java.util collections are fail-fast; most java.util.concurrent ones are fail-safe.

How Fail-Fast Works

Collections like ArrayList, HashMap, HashSet, and LinkedHashMap maintain an int modCount field that increments on every structural modification (add, remove, clear). When you create an iterator, it stores the current modCount as expectedModCount. Every next() call compares them:

// Sketch from ArrayList.Itr
final void checkForComodification() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

So this code fails:

List<Integer> xs = new ArrayList<>(List.of(1, 2, 3, 4));
for (Integer x : xs) {
    if (x == 2) xs.remove(x);   // ConcurrentModificationException
}

The correct way is to use the iterator's own remove:

var it = xs.iterator();
while (it.hasNext()) {
    if (it.next() == 2) it.remove();   // OK — updates expectedModCount
}
// Or, even better:
xs.removeIf(x -> x == 2);

How Fail-Safe Works

Fail-safe iterators come in two flavors:

Snapshot-based (e.g., CopyOnWriteArrayList): the iterator captures a reference to the immutable backing array at creation time. Subsequent mutations replace the array but don't affect the iterator. You never see new elements added after iteration began, and you can never call remove through the iterator.

List<String> log = new CopyOnWriteArrayList<>(List.of("a", "b"));
var it = log.iterator();
log.add("c");                          // safe — iterator unaffected
while (it.hasNext()) System.out.println(it.next());  // prints a, b

Weakly-consistent (e.g., ConcurrentHashMap, ConcurrentLinkedQueue): the iterator walks the live structure but tolerates concurrent mutation. It is guaranteed to visit each element that existed at iterator creation exactly once, and may or may not reflect modifications made after creation. It never throws ConcurrentModificationException.

var map = new ConcurrentHashMap<String, Integer>();
map.put("a", 1); map.put("b", 2);
for (var e : map.entrySet()) {
    map.put("c", 3);   // safe — may or may not appear in this iteration
}

Side-by-Side

AspectFail-fastFail-safe
Detection mechanismmodCount vs expectedModCountNone — tolerates mutation
On concurrent mutationThrows ConcurrentModificationExceptionNever throws
Sees new elements?N/A — already crashedSnapshot: no; weakly-consistent: maybe
ExamplesArrayList, HashMap, HashSetCopyOnWriteArrayList, ConcurrentHashMap, ConcurrentLinkedQueue
Memory costNone extraSnapshot copies the array
Best forSingle-threaded; catching bugsConcurrent reads alongside writes

Fail-Fast is Best-Effort

The Javadoc is explicit: fail-fast is not a correctness guarantee, just a debugging aid. In multi-threaded code without synchronization, a fail-fast iterator might miss a concurrent modification and produce garbage results. Never rely on ConcurrentModificationException to enforce thread safety.

Mark your status