Fix the loop that drops items from a list while iterating it
This snippet should drop every three-letter name from the list and print what is left. It compiles and runs in a single thread, yet it fails at run time instead of printing.
Constraints:
- keep an
ArrayList; the result must be the original contents minus the three-letter names - do not swallow the failure with a
try/catch
List<String> names = new ArrayList<>(List.of("ann", "bob", "carol", "dan"));
for (String name : names) {
if (name.length() == 3) {
names.remove(name);
}
}
System.out.println(names);
Find and fix the bug.
The for-each loop runs on the list's own fail-fast Iterator. names.remove(name) bumps the list's modCount behind the iterator's back, so the next next() finds modCount != expectedModCount and throws ConcurrentModificationException — no second thread is involved. Fix it by mutating through the iterator (it.remove() after it.next()), or simply call names.removeIf(n -> n.length() == 3); iterating over a copy also works.
- ✗Believing
ConcurrentModificationExceptiononly ever happens with multiple threads - ✗Calling
list.remove(...)inside a for-each instead ofIterator.remove() - ✗Assuming the exception is guaranteed — removing the second-to-last element can slip through
- →Why does removing the second-to-last element sometimes avoid the exception entirely?
- →How do the iterators of
CopyOnWriteArrayListandConcurrentHashMapavoid this failure?
Fix
List<String> names = new ArrayList<>(List.of("ann", "bob", "carol", "dan"));
// Option 1 — remove through the iterator itself
for (Iterator<String> it = names.iterator(); it.hasNext(); ) {
if (it.next().length() == 3) {
it.remove(); // the iterator refreshes its own expectedModCount
}
}
// Option 2 — the shortest: removeIf does exactly this internally
names.removeIf(n -> n.length() == 3);
// Option 3 — iterate a copy, remove from the original
for (String name : new ArrayList<>(names)) {
if (name.length() == 3) {
names.remove(name);
}
}
System.out.println(names); // [carol]
What was happening. A for-each loop is sugar over names.iterator(). ArrayList counts structural changes in a modCount field, and its iterator snapshots that value into expectedModCount when it is created. names.remove(name) goes around the iterator and bumps modCount; on the next next(), checkForComodification() sees the mismatch and throws ConcurrentModificationException.
This is not a concurrency problem: one thread is enough. The name is historical — it means "the collection was modified concurrently with the traversal", not "by two threads".
Why the exception is not guaranteed. The check lives in next(), while hasNext() is merely cursor != size. Remove the second-to-last element and cursor equals the new size, so hasNext() returns false, the loop ends quietly one step early — and nothing throws at all. That is why the exception must never be treated as a reliable error signal.