Course contents

Fail-fast vs fail-safe iterators

Fail-fast iterators, `modCount` and `expectedModCount`, best-effort detection, snapshot iterators, weakly consistent iterators and when `CopyOnWriteArrayList` fits.

PremiumMust know for interviewsLesson13 min readCoreReviewed Oct 2026 · Java 21
On this page
  1. Picture it first: a museum alarm, a photo and a live camera
  2. Fail-fast under the hood: modCount and expectedModCount
  3. Best-effort means not a concurrency control
  4. "Fail-safe" is informal: two real iterator families
  5. Guide to CopyOnWriteArrayList: when I would use it
  6. Edge cases and comparisons interviewers probe
  7. How to say it in the interview

🧯 What are fail-fast and fail-safe iterators?

A fail-fast iterator tries to detect that its collection was structurally changed behind its back, then throws ConcurrentModificationException quickly instead of continuing with stale assumptions. An ArrayList iterator, for example, remembers the list's modification count when it starts. "Fail-safe" is not an official JDK contract; people usually mean either a snapshot iterator, like CopyOnWriteArrayList, or a weakly consistent iterator, like ConcurrentHashMap.

For this lesson, we'll focus on how modCount and expectedModCount work, why fail-fast is best-effort rather than a thread-safety guarantee, and what people really mean by "fail-safe". We'll cover CopyOnWriteArrayList here because it is the common checklist item, but its deeper concurrency design belongs in the Concurrency module. Interviewers ask this because the phrase sounds simple, then the edge cases reveal whether you know the mechanism.

Below the line (out of scope)

The 30-second answer

Fail-fast iterators, such as the usual ArrayList, HashSet and HashMap iterators, store expectedModCount when they are created. Structural changes to the collection increment modCount; next() compares the two and throws ConcurrentModificationException if they differ. This is best-effort bug detection, not a lock and not a concurrency-control strategy, so you still need synchronization or a concurrent collection when threads share mutable state. "Fail-safe" is informal. Snapshot iterators, such as CopyOnWriteArrayList and CopyOnWriteArraySet, iterate over an old array, never throw CME, do not support iterator.remove(), and copy the whole array on writes. Weakly consistent iterators, such as ConcurrentHashMap, ConcurrentLinkedQueue and ConcurrentSkipListMap, may reflect some concurrent updates, never throw CME, and are documented not to return an element twice.

What you’ll learn

  • Explain how fail-fast iterators compare modCount with expectedModCount
  • Say why fail-fast is best-effort, not a concurrency-control mechanism
  • Separate informal fail-safe into snapshot iterators and weakly consistent iterators
  • Choose CopyOnWriteArrayList for read-mostly listener and configuration lists
  • Describe the write cost and Iterator.remove() limitation of copy-on-write collections
  • Answer concurrent iteration scenarios with synchronization or concurrent collections, not hope

Interview radar

Asked very often: Why does a for-each loop throw ConcurrentModificationException when I remove from the list? Senior rounds probe: Fail-fast is best-effort, "fail-safe" is informal, and weakly consistent is not the same thing as snapshot. Production angle: A background refresher updates an orders list while a request thread iterates it. The right fix is a concurrency design, not hoping CME saves you.

01Picture it first: a museum alarm, a photo and a live camera

Think of a fail-fast iterator as a museum guard checking whether the room's seal number still matches the number on their clipboard. If another guard moves a painting, the seal changes and the next check trips an alarm. The alarm is helpful, but it is not the lock on the door.

Snapshot iteration is different: you walk around with a photo taken at 10:00. If the room changes at 10:01, your photo does not change. Weakly consistent iteration is closer to a live camera feed: you may see some changes as they happen, but the system avoids corrupt traversal and does not promise a perfect instant-in-time view.

Fig. 1

A fail-fast iterator compares expectedModCount with modCount on next, while a snapshot iterator reads an old array and a weakly consistent iterator traverses a concurrent structure that may show updates

A fail-fast iterator compares expectedModCount with modCount on next, while a snapshot iterator reads an old array and a weakly consistent iterator traverses a concurrent structure that may show updates

Notice the shape. Fail-fast is about detecting illegal structural change in ordinary collections. Snapshot and weakly consistent iterators are about letting iteration continue under a different contract.

02Fail-fast under the hood: modCount and expectedModCount

Most general-purpose mutable collections in java.util keep a structural modification counter. ArrayList inherits modCount from AbstractList; HashMap has its own. Adding, removing, clearing or resizing changes the shape of the collection, so the counter increments. Replacing an existing value, such as list.set(i, value) or map.put(existingKey, newValue), usually does not change the structure.

Try it yourself first

Before reading on, predict which line throws. Also decide whether the loop is "concurrent" in the thread sense.

List<String> orders = new ArrayList<>(List.of("new", "paid", "shipped"));

for (String status : orders) {                 // creates an Iterator
    if (status.equals("paid")) {
        orders.add("audited");                // structural change outside the iterator
    }
}

The loop usually throws ConcurrentModificationException on the next iterator operation after add. There is only one thread here. ConcurrentModificationException is named after concurrent modification of the collection and iterator, not necessarily concurrent execution by two threads.

Conceptually, the iterator looks like this:

final class ArrayListIterator<E> implements Iterator<E> {
    private int cursor;
    private int expectedModCount = list.modCount;

    public E next() {
        if (list.modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
        return list.elementData[cursor++];
    }

    public void remove() {
        list.remove(--cursor);
        expectedModCount = list.modCount;      // iterator performed the change
    }
}

Premium lesson

Keep reading with the Java Pass

The rest of this lesson is part of Java One-Stop Interview Prep: in-depth explanations, diagrams, model answers and how to say them in the interview.

  • Best-effort means not a concurrency control
  • "Fail-safe" is informal: two real iterator families
  • Guide to CopyOnWriteArrayList: when I would use it
  • Edge cases and comparisons interviewers probe
  • How to say it in the interview

From ₹199 · one-time payment · No auto-renewal

Practise this topicOrdering & iteration: interview questions & scenarios
Further reading (6)