Collection Modified While Iterating
ID |
python.collection_while_iterating |
Severity |
high |
Remediation Complexity |
trivial |
Remediation Risk |
low |
Remediation Effort |
low |
Resource |
Reliability |
Language |
Python |
Tags |
reliability, suspicious-construct |
Description
Reports modifications of the iterated collection inside a for loop’s body — items.append(x), items.remove(x), items.pop(), etc. on the collection being iterated. The iterator state desyncs from the underlying container, and the loop either silently skips elements (lists) or raises RuntimeError: dictionary changed size during iteration (dicts/sets).
for item in items:
if condition(item):
items.remove(item) # FLAW
for key in mapping:
mapping.pop("__internal", None) # FLAW
for item in list(items): # OK — iterating a copy
items.remove(item)
removed = [item for item in items if condition(item)] # OK — comprehension
items = [item for item in items if not condition(item)] # OK — rebuild
Rationale
The bug is timing-sensitive and almost impossible to reproduce in unit tests: the loop misses some elements, but only ones whose index is shifted by the mutation. Static detection is the only reliable defence.
Remediation
Either iterate over a copy of the collection (for item in list(items):) or build a new collection through a comprehension and reassign:
items = [item for item in items if not condition(item)]
For dicts, build a list of keys to remove and apply the changes after the loop:
to_remove = [k for k in mapping if predicate(k)]
for k in to_remove:
del mapping[k]