Explanation
What just happened
A for-each loop uses a hidden Iterator. At bob, the loop called names.remove, and the remove worked: the list became [ann, cid, dan]. On the next step, the iterator checked whether the list had changed since the loop began. It had, so it threw ConcurrentModificationException instead of guessing where to continue. No other thread was involved.
Try the what-if switch
- removeIf: the list walks and removes in one call. No exception.
- another thread adds: a second thread adds
99mid-loop. The exception is thrown inmain, the thread that iterates, not in the one that changed the list. - CopyOnWriteArrayList: every write makes a new copy, and the loop walks the copy it started with. No exception, and the loop never sees
99.
Why it fails fast
Once the list has shifted, the iterator can’t know which element comes next. Throwing right away is safer than silently skipping or repeating elements.
Watch out
The check is “best effort”: removing the second-to-last element skips the last one without any exception.
Common questions
What causes ConcurrentModificationException?
Changing a list while a for-each loop is walking it, usually with list.remove() inside the loop. The loop's hidden iterator notices on its next step and throws. One thread is enough.
How do I remove items while looping?
Use list.removeIf(...), or loop with an explicit Iterator and call iterator.remove(). Both remove without confusing the loop.
Why didn't my loop throw when I removed an element?
If you remove the second-to-last element, the loop thinks it has reached the end and stops without throwing. The last element is never visited, which is a quieter bug.
Verified: this timeline matches a real program, checked in CI on JDK 21 and 25. How we verify. Unclear or wrong? Tell us