Home โบ Errors โบ RuntimeError
RuntimeError: dictionary changed size during iteration
Exception: RuntimeErrorCategory: runtime (mutation during iteration)Raised at: the point in the loop where the dict's size no longer matches the iteration snapshot
What this error means
You added or removed keys from a dictionary while looping over it. Python detects that the dict's size changed mid-iteration and stops, because continuing would produce unpredictable results.
Traceback (most recent call last):
File "demo.py", line 3, in <module>
for k, v in scores.items():
RuntimeError: dictionary changed size during iteration
scores = {"asha": 91, "ravi": 42, "meera": 77}
# โ the classic failure
for name, marks in scores.items():
if marks < 50:
del scores[name] # ๐ฅ RuntimeError on the next iteration
# โ adding is just as bad
for name in scores:
if name.startswith("a"):
scores[name + "_x"] = 0 # ๐ฅ same error
Note that the error is raised on the next iteration step, not on the del line itself. That makes the traceback point at the for statement, which is why it reads as though the loop is the problem when the mutation is.
Why Python refuses
A dict iteration walks the underlying hash table. Inserting or deleting can rehash and reorder it, so the iterator's position becomes meaningless โ it might skip entries, repeat them, or run off the end. Rather than silently give you wrong results, Python raises.
This is a deliberate design choice, and it is a good one. The alternative โ what Python 2 did in some cases โ was to iterate unpredictably, which produced bugs that looked like data corruption.
# the same rule applies to sets
s = {1, 2, 3}
for x in s:
s.discard(x) # ๐ฅ Set changed size during iteration
# lists do NOT raise โ but deleting while iterating is still a bug
nums = [1, 2, 3, 4]
for n in nums:
if n % 2 == 0:
nums.remove(n) # โ ๏ธ no error, but skips items
print(nums) # [1, 3] by luck, not by design
That last example is worth internalising: lists silently misbehave instead of raising. The dict error is Python protecting you; the list's silence is not.
Fix 1 โ collect the keys, then act
The most readable fix. Build a list of what you want to change, then apply the changes after the loop has finished:
scores = {"asha": 91, "ravi": 42, "meera": 77}
to_delete = [name for name, marks in scores.items() if marks < 50]
for name in to_delete:
del scores[name]
# or in one line
for name in [n for n, m in scores.items() if m < 50]:
del scores[name]
# adding works the same way
new_keys = [f"{n}_backup" for n in scores]
for k in new_keys:
scores[k] = 0
The comprehension takes a snapshot of the keys as a plain list, so the loop afterwards is iterating a list โ not the dict โ and mutating the dict is safe.
Fix 2 โ build a new dict (usually the best answer)
When you are *filtering*, do not mutate at all. Build the dict you want:
scores = {"asha": 91, "ravi": 42, "meera": 77}
# dict comprehension โ one line, no mutation, no error
passed = {name: marks for name, marks in scores.items() if marks >= 50}
# transforming values at the same time
curved = {n: m + 5 for n, m in scores.items() if m >= 50}
scores = passed # rebind if you wanted to replace the original
This is the fix to reach for first. It is shorter than the collect-then-delete version, it cannot go wrong, and it leaves the original intact in case something else still needs it. Mutating in place is only better when the dict is shared and other references must see the change.
Fix 3 โ iterate over a copy
scores = {"asha": 91, "ravi": 42}
# .copy() gives a shallow snapshot of the whole dict
for name, marks in scores.copy().items():
if marks < 50:
del scores[name] # โ
the loop is over the copy
# list(d.items()) does the same, slightly cheaper
for name, marks in list(scores.items()):
if marks < 50:
del scores[name] # โ
scores.copy() and list(scores.items()) both work. The copy is clearer to read; list(...) avoids building a second dict. Neither is wrong โ pick whichever reads better in context.
Fix 4 โ pop with a default
When you are removing known keys rather than filtering, pop with a default avoids the KeyError and reads more directly:
for name in ["ravi", "imran", "unknown"]:
scores.pop(name, None) # โ
no error if the key is absent
# and for the whole filter, no loop over the dict at all
scores = {k: v for k, v in scores.items() if v >= 50}
The nested-dict version
The same bug appears one level down, and it is easy to miss because the mutation is buried inside the inner loop:
students = {
"class_a": {"asha": 91, "ravi": 42},
"class_b": {"meera": 77},
}
# โ
for cls, marks in students.items():
for name, score in marks.items():
if score < 50:
del marks[name] # ๐ฅ inner dict changed size
# โ
filter the inner dicts, rebuild the outer
students = {
cls: {n: s for n, s in marks.items() if s >= 50}
for cls, marks in students.items()
}
# โ
or snapshot the inner keys
for cls, marks in students.items():
for name in [n for n, s in marks.items() if s < 50]:
del marks[name]
If a loop is more than two levels deep and you are mutating inside it, that is usually the signal to build a new structure instead. It reads better and it cannot hit this error.
Related messages worth knowing
- KeyError: the parent guide โ what you get when the key you meant to delete is not there.
- TypeError โ the parent class of most runtime type failures.
- ValueError โ the parent class of RuntimeError's more common sibling.
๐ฎ๐ณ Hindi เคฎเฅเค เคธเคฎเคเฅเค
RuntimeError: dictionary changed size during iteration tab aata hai jab aap dict par loop chala rahe ho aur usi dauraan keys add ya delete kar dete ho. Python detect kar leta hai ki beech me size badal gaya, aur rok deta hai โ kyunki aage badhne par nateeja unpredictable hoga. Dhyan do: error del wali line par nahi, agle iteration step par aata hai. Isliye traceback for statement ko point karta hai, aur lagta hai loop me problem hai jabki asli problem mutation hai. Python mana kyun karta hai? Dict iteration hash table par chalti hai; insert ya delete usse rehash aur reorder kar sakta hai, to iterator ki position ka matlab hi khatm ho jaata hai โ entries skip ho sakti hain, repeat ho sakti hain. Galat nateeja chupchaap dene ke bajaye Python error deta hai, jo acchi design hai. Ek comparison: lists error nahi deti, par delete karte waqt items skip karti hain โ dict ka error aapki raksha hai, list ki chuppi nahi. Chaar safe tareeke: (1) pehle keys ki list banao, phir loop ke baad delete karo. (2) Naya dict banao โ {k: v for k, v in d.items() if v >= 50} โ ye sabse behtar hai, chhota hai, galat ho hi nahi sakta, aur original bacha rehta hai. (3) scores.copy() ya list(scores.items()) par loop chalao โ copy par loop chalta hai, isliye original badalna safe hai. (4) pop(name, None) se known keys hatao. Nested dict me yahi bug ek level neeche aata hai aur pakadna mushkil hota hai; wahan inner dict ko comprehension se rebuild karna sabse saaf hai.