Mastery
Mastery/Java/C. Collections framework
T1 · high-leverage
Compare with Python: Mutating a collection while iterating it

Fail-fast iterators: ConcurrentModificationException — and when it DOESN'T fire

ArrayList's iterator checks a modCount on every next() call and throws ConcurrentModificationException if the list was structurally modified outside the iterator. The commonly-missed detail: this check only runs inside next(). If you remove an element such that hasNext() becomes false right after, the loop ends before next() is called again — so the exception silently doesn't fire, and you get a subtly-wrong result instead of a loud error. This is real, verified behavior, not a hypothetical.

java
import java.util.*;

System.out.println("case A: [1,2,3], remove value 2 (second-to-last) mid for-each");
List<Integer> a = new ArrayList<>(List.of(1, 2, 3));
try {
    for (Integer v : a) { if (v == 2) a.remove(v); }
    System.out.println("  NO exception thrown. list silently ends up = " + a + "  <-- the trap");
} catch (ConcurrentModificationException e) {
    System.out.println("  CME: " + e);
}

System.out.println("case B: [1,2,3,4], remove value 2 (NOT second-to-last)");
List<Integer> b = new ArrayList<>(List.of(1, 2, 3, 4));
try {
    for (Integer v : b) { if (v == 2) b.remove(v); }
    System.out.println("  no exception. list now = " + b);
} catch (ConcurrentModificationException e) {
    System.out.println("  CME: " + e);
}

The actual correct fix — always use the iterator's own remove(), which updates modCount in sync so no mismatch is ever detected:

java
List<Integer> d = new ArrayList<>(List.of(1, 2, 3, 4));
Iterator<Integer> it = d.iterator();
while (it.hasNext()) {
    if (it.next() == 2) it.remove();
}
System.out.println("Iterator.remove() result: " + d);

Interview angle

A classic "find the bug" exercise — a for-each loop that calls list.remove() inside it — but the strong version of this question (the one this topic demonstrates) probes whether the candidate knows fail-fast detection isn't 100% reliable. A candidate who confidently says "that always throws CME" is exposing exactly the kind of half-knowledge that turns into a real, position-dependent production bug.

In the industry

This is a genuinely common production bug, and an unsettling one because it's position-dependent — it might pass a quick manual test and only misbehave on certain input sizes, which is why it sometimes survives into production as a "flaky" issue before someone traces it back. The correct, idiomatic fix — Iterator.remove() or Collection.removeIf() — is something any experienced Java reviewer checks for on sight in a loop that mutates the collection it's iterating.