skip to content

What happens if you remove or add elements to a collection while iterating it with a for-each loop, and how do you remove safely?

level: middleimportance: must knowfreq 75%

answer

  1. Structural change in for-each -> CME
  2. Fail-fast iterators track modCount
  3. Safe: Iterator.remove() or removeIf()
  4. CME is best-effort, not a concurrency guard
  5. Concurrent collections are weakly consistent (no CME)

basics

~10 s

Changing the collection's size during a for-each usually throws a ConcurrentModificationException. To remove items safely, use an explicit Iterator and call its remove(), or use collection.removeIf(...).

solid answer

~40 s

Most standard collections return fail-fast iterators: they keep a modification counter (modCount), and if the collection is structurally changed (add/remove) by anything other than the iterator itself during a loop, the next hasNext()/next() throws ConcurrentModificationException. Since for-each hides the iterator, you can't call its remove(), so structurally mutating inside a for-each is unsafe. The standard fixes: (1) iterate with an explicit Iterator and call it.remove(); (2) use the Collection.removeIf(predicate) method (Java 8+), which is the cleanest; or (3) collect items to delete and remove them after the loop, or iterate a copy. Note CME is best-effort, not guaranteed — it's a detection aid, not a thread-safety mechanism; never rely on its absence to mean your code is correct under concurrency.

code

java · 13 lines
java
List<String> names = new ArrayList<>(List.of("", "ann", " ", "bob"));

// WRONG: throws ConcurrentModificationException
// for (String n : names) { if (n.isBlank()) names.remove(n); }

// RIGHT 1: explicit iterator
Iterator<String> it = names.iterator();
while (it.hasNext()) {
    if (it.next().isBlank()) it.remove();
}

// RIGHT 2 (preferred): removeIf
names.removeIf(String::isBlank);

go deeper

for a junior

Knows removing from a list while looping it can crash and that removeIf is a safe alternative.

for a middle

Explains fail-fast/modCount, why Iterator.remove() is safe, and lists the safe-removal options.

for a senior

Notes CME is best-effort, distinguishes fail-fast from weakly-consistent concurrent collections, and chooses removeIf for clarity/perf.

for a principal

Reasons about modification semantics across collection families, concurrency correctness, and sets team conventions to avoid CME-class bugs.

## The problem A **structural modification** is one that changes a collection's size — adding or removing elements. If you do this while a for-each loop is walking the collection, you typically get a **`ConcurrentModificationException` (CME)**. ```java for (String s : list) { if (s.isBlank()) list.remove(s); // throws CME } ``` ## Why: fail-fast iterators Most `java.util` collections (`ArrayList`, `HashMap`, `HashSet`, …) produce **fail-fast** iterators. The collection keeps an internal counter called **`modCount`** that increments on every structural change. When you create an iterator it records the current `modCount` as `expectedModCount`. On each `next()`/`hasNext()` it checks `modCount == expectedModCount`; if they differ, something changed behind the iterator's back and it throws CME immediately. This 'fails fast' so you find the bug right away instead of silently skipping or double-visiting elements. Because for-each **hides the iterator**, you have no handle to make a change *through* it — so any change to the collection is 'behind its back' and trips the check. (Curiously, removing the second-to-last element of an `ArrayList` can sometimes *not* throw, because `hasNext()` returns false before the check runs — another reason never to rely on CME firing.) ## The safe ways to remove **1. Explicit Iterator + remove():** ```java Iterator<String> it = list.iterator(); while (it.hasNext()) { if (it.next().isBlank()) it.remove(); // legal: change THROUGH the iterator } ``` The iterator's own `remove()` updates `expectedModCount` too, so no CME. **2. removeIf (Java 8+, preferred):** ```java list.removeIf(String::isBlank); ``` Concise, correct, and the collection implements it efficiently. **3. Defer or copy:** ```java List<String> toRemove = new ArrayList<>(); for (String s : list) if (s.isBlank()) toRemove.add(s); list.removeAll(toRemove); ``` Or iterate a snapshot copy (`for (String s : new ArrayList<>(list))`) and mutate the original. ## Important caveats - **CME is best-effort.** The spec says fail-fast behaviour 'cannot be guaranteed' — treat a thrown CME as a bug signal, never absence of it as proof of correctness. - **Not a concurrency tool.** CME can also appear (or not) under multi-threaded access; for true concurrency use `CopyOnWriteArrayList`, `ConcurrentHashMap`, etc., whose iterators are **weakly consistent** and don't throw. - **Adding** during iteration is just as unsafe as removing. ## Glossary - **Fail-fast**: detects concurrent/structural modification and throws ASAP. - **Weakly consistent**: iterates over a snapshot-ish view, tolerating concurrent changes without CME. - **Structural modification**: any change to the number of elements.

  • Why does Iterator.remove() not throw CME when list.remove() inside a for-each does?
    The iterator's own remove() updates its expectedModCount to match the new modCount, so the consistency check still passes. A direct collection change bypasses that, leaving the counters out of sync.
  • Is ConcurrentModificationException guaranteed whenever you mutate during iteration?
    No. It's documented as best-effort fail-fast behaviour. It can fail to fire (e.g. removing the second-to-last ArrayList element), so never depend on it for program logic.

saying these in an interview costs you the question

  • Calling list.remove() inside a for-each and expecting it to work
  • Treating CME as a thread-safety mechanism
  • Assuming CME always fires when you mutate mid-loop
  • Thinking iterating a copy then mutating the copy removes from the original

context