Pulling the rug out from under an iterator.
Imagine you are reading a list of names aloud, line by line. You are currently on line 3. Suddenly, another person runs up, grabs the list, and deletes line 2. All the names shift up. The name that was on line 4 is now on line 3. But you already read line 3! You will completely skip a name. If you delete line 4 instead, you might read the same name twice. This is what happens when a thread modifies a collection while another thread is iterating over it.
In languages like Java or Python, collections track a "modification count" internally. When an iterator starts, it remembers this count. On every loop iteration, it checks if the count has changed. If it has, it immediately throws a ConcurrentModificationException (or RuntimeError in Python) rather than returning corrupted data.
# The Bug
active_users = ["Alice", "Bob", "Charlie"]
for user in active_users: # Thread A reads
print(user)
# Thread B suddenly does: active_users.remove("Bob")
# ERROR! The iterator is now broken.
# The Fix (Copy on Read)
# Create a snapshot copy of the list to iterate over
for user in list(active_users):
print(user)
The standard fixes are either to Lock the collection (destroying performance if iteration takes a long time) or to Copy the collection before iterating (consuming extra memory). For highly concurrent code, you should use specialized lock-free data structures like Java's ConcurrentHashMap.
for loop that removes items from the same list it is iterating over, you will trigger the exact same bug/exception. Always iterate over a copy, or use a list comprehension to build a new list.