Back to Iterator & Removing During Iteration
Q1Question 1 of 3

What is fail-fast vs fail-safe iteration?

Key Concepts
  • Fail-fast iterators (ArrayList, HashMap, HashSet) throw ConcurrentModificationException if the collection is structurally modified during iteration
  • The detection mechanism is modCount: the iterator remembers expectedModCount at creation and checks it on every next() call
  • Iterator.remove() is safe because it updates both modCount and expectedModCount simultaneously
  • Fail-safe (weakly consistent) iterators (ConcurrentHashMap, CopyOnWriteArrayList) never throw CME
  • CopyOnWriteArrayList's iterator works on a snapshot taken at creation; ConcurrentHashMap's iterator tolerates concurrent changes
  • Fail-fast catches bugs early; fail-safe allows concurrent access but may show stale data
  • Java 8 removeIf() is the cleanest solution for safe removal during iteration

Definition

Fail-fast iterators throw ConcurrentModificationException when the collection is modified during iteration; fail-safe iterators never throw CME because they work on snapshots or tolerate concurrent changes.
  • Fail-fast (ArrayList, HashMap, HashSet): iterator checks modCount on every next() call; if the collection was structurally modified (add/remove) by anything other than iterator.remove(), it throws CME
  • Fail-safe (ConcurrentHashMap, CopyOnWriteArrayList): iterators never throw CME; CopyOnWriteArrayList iterates a snapshot, ConcurrentHashMap uses weakly consistent traversal
  • modCount mechanism: every structural modification increments modCount; the iterator stores expectedModCount at creation and verifies on each step
  • iterator.remove(): increments both modCount and expectedModCount, keeping them in sync

Fail-fast is like a bank clerk counting cash in the vault. If anyone opens the vault door during the count, the clerk stops immediately: "Someone touched the vault!" (CME). This catches problems early. Fail-safe is like counting a photocopy of the vault ledger. Even if someone adds cash to the real vault, the clerk continues counting the photocopy peacefully. No alarm, but the count might be slightly outdated.

Understanding the Concepts

What is fail-fast vs fail-safe iteration?

Fail-fast and fail-safe describe how iterators respond when the underlying collection is modified during iteration.

| Feature | Fail-fast | Fail-safe (weakly consistent) |
|---|---|---|
| Collections | ArrayList, HashMap, HashSet, LinkedList | ConcurrentHashMap, CopyOnWriteArrayList |
| Throws CME | Yes (on structural modification) | Never |
| Mechanism | modCount check on every next() | Snapshot or tolerant traversal |
| Reflects changes | N/A (throws before seeing them) | May or may not see concurrent changes |
| Memory overhead | None | COWAL: full array copy; CHM: none |
| Thread safety | Not thread-safe | Thread-safe |

JAVA
package com.bank.demo;

import java.util.*;
import java.util.concurrent.*;

public class FailFastVsFailSafeDemo {
    public static void main(String[] args) {
        // FAIL-FAST: ArrayList
        List<String> accounts = new ArrayList<>(List.of("SAV2001", "CLOSED-001", "CUR3001"));
        try {
            for (String acc : accounts) {
                if (acc.startsWith("CLOSED")) {
                    accounts.remove(acc); // CME!
                }
            }
        } catch (ConcurrentModificationException e) {
            System.out.println("ArrayList: " + e.getClass().getSimpleName());
        }

        // FAIL-SAFE: ConcurrentHashMap
        ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();
        map.put("SAV2001", "Active");
        map.put("CLOSED-001", "Closed");
        map.put("CUR3001", "Active");

        for (var entry : map.entrySet()) {
            if (entry.getValue().equals("Closed")) {
                map.remove(entry.getKey()); // no CME!
            }
        }
        System.out.println("ConcurrentHashMap after: " + map);

        // FAIL-SAFE: CopyOnWriteArrayList
        List<String> cowl = new CopyOnWriteArrayList<>(List.of("SAV2001", "CLOSED-001", "CUR3001"));
        for (String acc : cowl) {
            if (acc.startsWith("CLOSED")) {
                cowl.remove(acc); // no CME! Iterator uses snapshot.
            }
        }
        System.out.println("COWAL after: " + cowl);
    }
}
TEXT
ArrayList: ConcurrentModificationException
ConcurrentHashMap after: {SAV2001=Active, CUR3001=Active}
COWAL after: [SAV2001, CUR3001]

How does the modCount mechanism work?

Every ArrayList, HashMap, and HashSet maintains an internal modCount field. Every structural modification (add, remove, clear) increments this counter. When an iterator is created, it stores the current modCount as expectedModCount. On every call to next(), the iterator checks:

JAVA
// Simplified from OpenJDK ArrayList.Itr
public E next() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
    // ... return next element
}

public void remove() {
    // ... remove the element
    expectedModCount = ++modCount; // keep in sync!
}

iterator.remove() is safe because it increments BOTH modCount and expectedModCount, keeping them synchronized.

What are the safe ways to remove during iteration?

JAVA
package com.bank.demo;

import java.util.*;

public class SafeRemovalDemo {
    public static void main(String[] args) {
        List<String> accounts = new ArrayList<>(List.of("SAV2001", "CLOSED-001", "CUR3001", "CLOSED-002"));

        // Method 1: Iterator.remove()
        Iterator<String> it = accounts.iterator();
        while (it.hasNext()) {
            if (it.next().startsWith("CLOSED")) {
                it.remove();
            }
        }
        System.out.println("Iterator.remove: " + accounts);

        // Reset for next demo
        accounts = new ArrayList<>(List.of("SAV2001", "CLOSED-001", "CUR3001", "CLOSED-002"));

        // Method 2: removeIf (Java 8) – cleanest
        accounts.removeIf(acc -> acc.startsWith("CLOSED"));
        System.out.println("removeIf: " + accounts);

        // Method 3: Collect and remove after
        accounts = new ArrayList<>(List.of("SAV2001", "CLOSED-001", "CUR3001", "CLOSED-002"));
        List<String> toRemove = new ArrayList<>();
        for (String acc : accounts) {
            if (acc.startsWith("CLOSED")) toRemove.add(acc);
        }
        accounts.removeAll(toRemove);
        System.out.println("removeAll: " + accounts);

        // Method 4: Stream filter (create new list)
        accounts = new ArrayList<>(List.of("SAV2001", "CLOSED-001", "CUR3001", "CLOSED-002"));
        List<String> active = accounts.stream()
            .filter(acc -> !acc.startsWith("CLOSED"))
            .toList();
        System.out.println("Stream filter: " + active);
    }
}
TEXT
Iterator.remove: [SAV2001, CUR3001]
removeIf: [SAV2001, CUR3001]
removeAll: [SAV2001, CUR3001]
Stream filter: [SAV2001, CUR3001]

What are common mistakes?

Mistake: Assuming CME is only thrown in multi-threaded code

JAVA
// WRONG assumption: "I have one thread, so no CME"
for (String s : list) {
    list.remove(s); // CME! Even in single-threaded code!
}
// CME is about structural modification during iteration,
// not about multiple threads.

What should a senior engineer know?

- CME is best-effort. The fail-fast behavior is not guaranteed under all circumstances. It should not be relied upon for correctness – it is a debugging aid, not a contract.

- Maps have the same behavior. HashMap.entrySet().iterator() is fail-fast. ConcurrentHashMap.entrySet().iterator() is weakly consistent.

- removeIf on Maps. You can use map.entrySet().removeIf(e -> e.getValue() < threshold) to safely remove map entries during iteration.

- for-each is syntactic sugar for Iterator. for (T t : collection) compiles to Iterator it = collection.iterator(); while (it.hasNext()) { T t = it.next(); }. That is why collection.remove() during for-each causes CME.

What are common interview follow-up questions?

  • "What causes ConcurrentModificationException?" – Structural modification (add/remove) during iteration, detected via modCount.
  • "Is CME only in multi-threaded code?" – No, it happens in single-threaded code when modifying during for-each.
  • "How do you safely remove during iteration?" – iterator.remove() or removeIf().
  • "What is weakly consistent?" – Iterator may or may not reflect concurrent modifications.

Visual Overview

DIAGRAM
  Fail-fast mechanism (ArrayList):

  list.iterator() → expectedModCount = modCount (e.g., 5)

  Iteration:
  next() → check: modCount == 5? YES → return element
  next() → check: modCount == 5? YES → return element
  list.remove(x) → modCount = 6
  next() → check: modCount(6) == expectedModCount(5)? NO → CME!

  iterator.remove():
  remove() → modCount = 6 AND expectedModCount = 6 → in sync → safe!

  Fail-safe (CopyOnWriteArrayList):
  iterator() → captures snapshot of internal array
  list.add(x) → creates NEW array (old snapshot unchanged)
  next() → reads from snapshot → no CME, but won't see new element

Key Takeaway

Fail-fast iterators catch bugs early by throwing ConcurrentModificationException when the collection is modified during iteration. The mechanism is simple: the iterator stores the expected modCount at creation and checks it on every next() call. If they differ, it throws CME. This happens even in single-threaded code – it is about structural modification during iteration, not about multiple threads.

Fail-safe iterators in ConcurrentHashMap and CopyOnWriteArrayList never throw CME. They trade accuracy for safety: CopyOnWriteArrayList's iterator works on a frozen snapshot, and ConcurrentHashMap's iterator tolerates concurrent modifications with weakly consistent semantics.

In the National Bank of India's batch processing system, Rajesh uses removeIf(acc -> acc.getStatus().equals("CLOSED")) to safely filter closed accounts from the active list. It is cleaner than manual iterator.remove() loops and avoids the CME trap entirely.

Remember one sentence: for-each + collection.remove() throws CME; use iterator.remove() or removeIf() instead.

---

Share
Use ← → arrow keys to navigate