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?
answer
- Structural change in for-each -> CME
- Fail-fast iterators track modCount
- Safe: Iterator.remove() or removeIf()
- CME is best-effort, not a concurrency guard
- Concurrent collections are weakly consistent (no CME)
basics
~10 sChanging 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 sMost 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 linesList<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
Knows removing from a list while looping it can crash and that removeIf is a safe alternative.
Explains fail-fast/modCount, why Iterator.remove() is safe, and lists the safe-removal options.
Notes CME is best-effort, distinguishes fail-fast from weakly-consistent concurrent collections, and chooses removeIf for clarity/perf.
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