Mastery
Mastery/Python/B. Iteration & generators
T1 · high-leverage

dict/set size cannot change during iteration

Adding or removing keys from a dict (or elements from a set) while iterating over it raises RuntimeError: dictionary changed size during iteration. CPython's dict iterator tracks a version counter on the dict; any operation that changes the dict's size bumps that counter and the next __next__() call notices the mismatch and raises. Note the precise rule: it's about size, not content — reassigning the value of an existing key is completely fine mid-loop.

python
d = {"a": 1, "b": 2}
try:
    for k in d:
        d["c"] = 3   # adds a NEW key -> changes size
except RuntimeError as e:
    print("RuntimeError:", e)

print()
d2 = {"a": 1, "b": 2}
for k in d2:
    d2[k] = d2[k] * 10   # only changes an EXISTING key's value -> size unchanged, totally fine
print("mutated values in place:", d2)

print()
s = {1, 2, 3}
try:
    for x in s:
        s.add(4)
except RuntimeError as e:
    print("RuntimeError (set):", e)

The safe pattern when you need to add/remove during a walk: iterate over a snapshot copy, or collect changes and apply them after the loop.

python
d3 = {"a": 1, "b": 2, "c": 3}
for k in list(d3):        # list(d3) snapshots the keys up front
    if d3[k] % 2 == 1:
        del d3[k]
print("safe removal via snapshot:", d3)

Interview angle

A strong "read this code and tell me what happens" question, because a correct answer requires actually understanding dict internals rather than having memorized "don't mutate a dict while iterating it." Good interviewers push further: does changing an existing key's value also raise? (No.) That's the detail that separates "knows the rule" from "understands the mechanism."

In the industry

This is one of the most common real RuntimeErrors in Python codebases that filter or clean up collections — exactly why list(d.items()) snapshotting, or rewriting the loop as a dict/list comprehension, is the idiomatic fix and shows up constantly in code review suggestions. An experienced reviewer treats any for k in some_dict: loop that also calls .pop(), del, or assigns a new key inside the loop body as an automatic red flag worth a second look, even before running it.