Resolving ConcurrentModificationException in Kotlin Mutable Collections
Learn how to diagnose and fix ConcurrentModificationException in Kotlin. This guide covers why for-each loops fail during element removal and provides safe alternatives like removeAll and MutableIterator.
06 Aug 2026, 10:23 UTC

The Problem: Structural Modification During Iteration
A ConcurrentModificationException occurs when you modify the structure of a collection—adding or removing elements—while iterating over it with a mechanism that tracks the collection's state, such as a for-each loop. This is a fail-fast mechanism designed to surface unpredictable traversal behavior early rather than corrupt data silently.
Diagnostic Matrix
| Symptom | Common Cause | Root Mechanism |
|---|---|---|
Crash during for (item in list) loop |
Calling list.remove(item) inside the loop |
Implicit iterator detects a structural change |
Skipped elements or IndexOutOfBoundsException |
Using for (i in 0..list.size) with removal |
Index shift after element removal |
| Crash in a multi-threaded environment | One thread iterates while another modifies | Non-thread-safe collection (e.g., ArrayList) |
Step-by-Step Resolution Path
Follow these checks in order to determine the most efficient fix for your specific use case.
Check 1: Is the removal based on a simple condition?
If you are iterating solely to remove elements that match a predicate, avoid manual loops entirely. Kotlin provides an idiomatic extension function that handles the iterator logic internally.
Fix: Use removeAll { ... }
// Avoid this:
for (user in users) {
if (user.isInactive) users.remove(user) // Throws ConcurrentModificationException
}
// Use this:
users.removeAll { it.isInactive }
Check 2: Do you need complex logic during removal?
If you must perform additional operations (such as logging or triggering events) while removing an item, you cannot use a standard for-each loop. You must use an explicit MutableIterator.
Fix: Use MutableIterator.remove()
Run this logic within the scope of the collection owner. Ensure you call remove() on the iterator, not the list.
val iterator = users.iterator()
while (iterator.hasNext()) {
val user = iterator.next()
if (user.shouldBeRemoved()) {
println("Removing user: ${user.id}")
iterator.remove() // Safe: updates the collection and iterator state
}
}
Check 3: Is the collection small and the logic complex?
If the logic is too complex for a predicate and you want to avoid the verbosity of an iterator, you can iterate over a shallow copy of the collection. This decouples the iteration state from the modification state.
Fix: Iterate over toList()
// .toList() creates a read-only snapshot of the current elements
for (user in users.toList()) {
if (complexValidation(user)) {
users.remove(user)
}
}
Risk: This creates a new list in memory. For collections with tens of thousands of elements, this increases Garbage Collection (GC) pressure and may degrade performance in hot paths.
Comparison of Strategies
| Method | Time Complexity | Space Complexity | Best Use Case |
|---|---|---|---|
removeAll { } |
O(n) | O(1) | Standard conditional cleanup |
MutableIterator |
O(n) | O(1) | Side effects during removal |
toList() copy |
O(n) | O(n) | Small lists, high logic complexity |
Verification and Limitations
To verify a fix, write a test that specifically triggers the structural change. For example, create a MutableList of 10 integers and remove all even numbers using your chosen method. If the loop completes without a ConcurrentModificationException and the final list size is 5, the implementation is stable. You can confirm the original failure first by calling list.remove() inside a for-each loop and observing the exception.
Limitations: These solutions apply to single-threaded modifications. If the exception occurs because a background thread modifies the list while the main thread iterates, these methods will still fail. In those cases, use thread-safe collections such as CopyOnWriteArrayList or wrap access in a synchronized block.
Escalation Criteria
If the following conditions persist, move beyond simple iterator fixes to concurrency primitives:
- The exception occurs randomly despite using
MutableIterator(indicates multi-threaded access). - Memory usage spikes significantly after implementing
toList(). - Performance profiling shows the removal loop is a primary bottleneck in the application.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.