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]