Fail-Fast vs Fail-Safe Iterators
Iterators in Java fall into two groups based on what they do when the collection changes during iteration. A fail-fast iterator throws ConcurrentModificationException as soon as it detects a change. A fail-safe iterator does not throw: it either works on a copy of the data or is designed to tolerate changes. In this lesson, you will learn how each works, which collections use which, and when to choose each.
Fail-Fast Iterators
The iterators of the ordinary collections in java.util, such as ArrayList, HashSet and HashMap, are fail-fast. They track the collection's modification count and throw ConcurrentModificationException if it changes (see "ConcurrentModificationException in Java"). Failing immediately is safer than continuing with data that might be inconsistent.
Fail-Safe Iterators
The concurrent collections in java.util.concurrent have iterators that don't throw:
CopyOnWriteArrayListandCopyOnWriteArraySet: every change creates a fresh copy of the internal array. An iterator keeps using the snapshot that existed when it was created, so it never sees later changes.ConcurrentHashMapandConcurrentLinkedQueue: their iterators are weakly consistent: they never throw, and they may or may not see changes made after they were created.
Example: Both Kinds Side by Side
Let's see an example that adds an element during iteration of two different lists.
import java.util.ArrayList;
import java.util.ConcurrentModificationException;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
public class FailFastSafe {
public static void main(String[] args) {
List<String> fast = new ArrayList<>(List.of("a", "b", "c"));
try {
for (String s : fast) {
if (s.equals("a")) {
fast.add("d");
}
}
} catch (ConcurrentModificationException e) {
System.out.println("ArrayList: fail-fast, exception thrown");
}
List<String> safe = new CopyOnWriteArrayList<>(List.of("a", "b", "c"));
int seen = 0;
for (String s : safe) {
seen++;
if (s.equals("a")) {
safe.add("d");
}
}
System.out.println("CopyOnWriteArrayList: saw " + seen
+ " elements, list is now " + safe);
}
}ArrayList: fail-fast, exception thrown
CopyOnWriteArrayList: saw 3 elements, list is now [a, b, c, d]Explanation: The ArrayList iterator detects the add and throws. The CopyOnWriteArrayList iterator keeps reading its original snapshot of 3 elements, so the loop finishes normally and doesn't see "d", although "d" really was added to the list.
Comparison
| Fail-fast | Fail-safe | |
|---|---|---|
| On modification during iteration | Throws ConcurrentModificationException | No exception |
| Sees changes made during iteration | Not applicable (it stops) | Snapshot: no. Weakly consistent: maybe |
| Extra memory | None | Copy-on-write collections copy the array on every change |
| Examples | ArrayList, HashSet, HashMap, LinkedList | CopyOnWriteArrayList, ConcurrentHashMap, ConcurrentLinkedQueue |
| Package | java.util | java.util.concurrent |
Common Mistakes
Switching to CopyOnWriteArrayList just to silence an exception in single-threaded code. Fix the loop instead; the copy costs memory and time.
Expecting a fail-safe iterator to show new elements. A snapshot iterator never does.
Relying on fail-fast behavior for correctness. It's a best-effort check meant to reveal bugs, not a guarantee.
Interview Corner
What is the difference between fail-fast and fail-safe iterators?
Fail-fast iterators, used by most java.util collections such as ArrayList and HashMap, throw ConcurrentModificationException if the collection is structurally modified during iteration other than through the iterator; they detect this with a modification count. Fail-safe iterators, used by concurrent collections such as CopyOnWriteArrayList and ConcurrentHashMap, don't throw: copy-on-write collections iterate over a snapshot, and concurrent maps provide weakly consistent iterators that may or may not reflect later changes.
Practice
- Easy: Change the
safeloop to remove "b" instead of adding "d". How many elements does the loop see, and what is the final list? - Harder: Iterate over a
ConcurrentHashMap<String, Integer>with two keys and add a third key inside the loop. Run it several times and describe what you observe.
Key Takeaways
Was this tutorial helpful?