Fail-Fast vs Fail-Safe Iterators

Naveen AnamaneniUpdated · 4 min read

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:

  • CopyOnWriteArrayList and CopyOnWriteArraySet: 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.
  • ConcurrentHashMap and ConcurrentLinkedQueue: their iterators are weakly consistent: they never throw, and they may or may not see changes made after they were created.
Fail-fast throws on change; a snapshot iterator keeps reading the old copy

Example: Both Kinds Side by Side

Let's see an example that adds an element during iteration of two different lists.

FailFastSafe.java▶ Try it
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);
    }
}
Output
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-fastFail-safe
On modification during iterationThrows ConcurrentModificationExceptionNo exception
Sees changes made during iterationNot applicable (it stops)Snapshot: no. Weakly consistent: maybe
Extra memoryNoneCopy-on-write collections copy the array on every change
ExamplesArrayList, HashSet, HashMap, LinkedListCopyOnWriteArrayList, ConcurrentHashMap, ConcurrentLinkedQueue
Packagejava.utiljava.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

  1. Easy: Change the safe loop to remove "b" instead of adding "d". How many elements does the loop see, and what is the final list?
  2. 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.

Quick check: Fail-fast vs fail-safe

4 questions
1Which iterator throws ConcurrentModificationException?
2Which collection has a snapshot iterator?
3Does a snapshot iterator see elements added during the loop?
4When is CopyOnWriteArrayList a good choice?

Key Takeaways

Was this tutorial helpful?

Lesson 257 of 482