skip to content

What is a ConcurrentModificationException, and when does it typically occur?

level: juniorimportance: must knowfreq 78%

answer

  1. modCount vs expectedModCount
  2. structural = changes size
  3. usually single-threaded for-each bug
  4. fix: Iterator.remove / removeIf / copy
  5. fail-fast = throw immediately

basics

~20 s

It's an error Java throws when you change a collection's structure (add or remove items) while looping over it with a for-each loop or iterator. The classic case: removing an element from a list inside a for-each loop.

solid answer

~40 s

ConcurrentModificationException (CME) signals that a collection was structurally modified while it was being iterated, in a way the iterator didn't expect. 'Structural' means adding or removing elements (changing the size), not just updating an existing element's value. The most common cause has nothing to do with multiple threads: it's modifying the collection directly (e.g. list.remove(x)) inside a for-each loop over that same collection. The for-each loop uses an iterator under the hood, and that iterator detects the modification on its next next() or remove() call and throws. The fix is to either iterate over a copy, use Iterator.remove(), use removeIf(), or collect-then-modify. Despite the 'Concurrent' name, it is most often a single-threaded bug.

code

java · 15 lines
java
List<String> list = new ArrayList<>(List.of("a", "b", "c"));

// WRONG: throws ConcurrentModificationException
for (String s : list) {
    if (s.equals("b")) list.remove(s);
}

// RIGHT (Java 8+):
list.removeIf(s -> s.equals("b"));

// RIGHT (explicit iterator):
Iterator<String> it = list.iterator();
while (it.hasNext()) {
    if (it.next().equals("b")) it.remove();
}

go deeper

for a junior

Recognizes the classic 'remove inside a for-each' bug and knows the standard fix (Iterator.remove or removeIf). Knows CME is usually a single-threaded mistake, not a threading issue.

for a middle

Can explain modCount/expectedModCount and what 'structural modification' precisely means (size change, not value change). Lists multiple correct fixes and knows set() is safe.

for a senior

Explains why fail-fast exists (avoid silent wrong results), that detection is best-effort and not a concurrency guarantee, and when to switch to concurrent collections instead.

for a principal

Frames CME as a designed bug-detector, contrasts fail-fast vs weakly-consistent iterators as a deliberate library trade-off, and guides teams on choosing data structures for concurrent access patterns.

## The problem in one picture In Java, a **collection** is an object that holds a group of elements (a `List`, `Set`, `Map`, etc.). To visit every element you use an **iterator** — an object that walks the collection one element at a time via `hasNext()` and `next()`. The `for (T x : collection)` syntax (the **for-each loop**) is just syntactic sugar: the compiler turns it into an explicit iterator loop. **ConcurrentModificationException (CME)** is a `RuntimeException` (unchecked — you don't have to declare or catch it) thrown when the collection is **structurally modified** while an iterator is walking it. ## What 'structural modification' means A **structural modification** changes the *number* of elements — i.e. anything that adds or removes elements and thus changes `size()`: `add`, `remove`, `clear`, `addAll`, etc. **Not** structural: replacing an existing element's value in place, e.g. `list.set(i, newValue)` or `map.put(existingKey, newValue)` — these keep the size the same and do **not** trigger CME. ## The classic bug (single-threaded) ```java List<String> list = new ArrayList<>(List.of("a", "b", "c")); for (String s : list) { // hidden iterator if (s.equals("b")) { list.remove(s); // structural modification of the SAME list } } // throws ConcurrentModificationException on the next iteration ``` Notice: **no threads involved.** You modified the list through the *list* while iterating through the *iterator*; the iterator notices the size changed underneath it and refuses to continue, because continuing could skip elements or read garbage. ## How the iterator notices (the mechanism) Most JDK collections keep an internal counter, **`modCount`** (modification count), incremented on every structural change. When you create an iterator it snapshots that value into `expectedModCount`. On each `next()` (and `remove()`) the iterator runs `checkForComodification()`: if `modCount != expectedModCount`, the collection changed behind its back, so it throws CME. This is called a **fail-fast** iterator: it fails immediately and loudly rather than silently producing wrong results. ## Why fail fast at all? If the iterator quietly kept going after a removal, it could skip the element right after the removed one, or index past the end. Surfacing a clear exception at the point of misuse is far better than a subtle, data-dependent bug. CME is a **bug detector**, not a feature you should catch and ignore. ## The correct fixes 1. **`Iterator.remove()`** — the iterator's own remove keeps `expectedModCount` in sync: ```java Iterator<String> it = list.iterator(); while (it.hasNext()) { if (it.next().equals("b")) it.remove(); } ``` 2. **`Collection.removeIf(predicate)`** (Java 8+) — concise and safe. 3. **Iterate a copy, modify the original:** `for (String s : new ArrayList<>(list)) { ... }`. 4. **Collect then modify:** gather the to-remove items in a separate list, then `removeAll`. ## The 'Concurrent' in the name is misleading The same exception *can* fire when one thread iterates while another structurally modifies the collection — but fail-fast detection is **best-effort and not guaranteed** for true concurrency (the `modCount` read isn't synchronized). So you must never *rely* on CME for thread-safety; for genuine multi-threaded access use the concurrent collections (`ConcurrentHashMap`, `CopyOnWriteArrayList`) whose iterators are **fail-safe / weakly consistent** and never throw CME.

  • Does calling list.set(i, value) inside a for-each loop throw CME?
    No. set() replaces an existing element without changing the size, so it is not a structural modification and does not bump modCount. (Note: for-each gives you no index, so set is awkward there anyway; the point is set() itself is CME-safe.)
  • Is CME a checked or unchecked exception?
    Unchecked — it extends RuntimeException, so you are not required to declare or catch it. That's deliberate: it indicates a programming bug, not a recoverable condition.

Like counting people in a room from a clipboard list: if someone walks out (or in) mid-count, your tally is now wrong, so you stop and raise your hand rather than report a bad number.

saying these in an interview costs you the question

  • Thinking CME only happens with multiple threads — it's usually a single-threaded for-each bug.
  • Believing list.set() during iteration causes CME — it doesn't, because it isn't a structural modification.
  • Catching CME and ignoring/retrying instead of fixing the modify-while-iterating code.
  • Assuming the JVM guarantees CME on every concurrent modification — detection is best-effort only.

context