FULL PYTHON FIX · 8 MIN READ
Fix RuntimeError: dictionary changed size during iteration
Deleting keys while looping over a dictionary invalidates its live iterator. Use a snapshot of keys for in-place deletion or build a new filtered dictionary, then verify the original-state rule.
Quick fix
For an intentional in-place change, make a separate list of keys with `list(scores)`.
scores = {'Ada': 92, 'Bo': 48, 'Cy': 75}
for name in list(scores):
if scores[name] < 60:
del scores[name]
print(scores)Video guide: Fix RuntimeError: dictionary changed size during iteration
The preview is stored on this site. YouTube loads only when you press Play. Watch on YouTube
Jump to a chapter
Read the video transcript
Select a timestamp to watch that moment on YouTube.
- 00:00
This program has three scores and wants to remove every entry below sixty. Bo has forty-eight, so the loop deletes that key. On the next step Python raises RuntimeError: dictionary changed size during iteration. It can feel strange that deletion succeeded but the loop still fails. The loop is walking a live dictionary iterator. Removing a key changes the structure that iterator expects to traverse, and Python detects the size change when advancing. The exception may therefore appear after one deletion has already modified the original dictionary. In a larger cleanup job, that leaves partly changed data.
- 00:40
We should not just catch RuntimeError and continue, because the iteration state is already unreliable. Instead, decide whether this operation is meant to edit the original dictionary or produce a separate filtered result. We will reproduce the failing run, inspect the behavior of dictionary views, and implement both safe patterns. Predict the output of each repaired version before we run it, including whether the original table changes. A dictionary's keys view stays connected to its dictionary. Before deleting Bo, converting that view to a list shows Ada and Bo. After deleting Bo, converting the same view again shows only Ada.
- 01:22
The view is not an independent snapshot. A plain `for name in scores` loop also traverses the dictionary's keys. If the set of keys changes during that active traversal, the iterator can raise RuntimeError. Be precise about the word changes: assigning a new value to an existing key keeps the number of keys unchanged, whereas adding or deleting a key changes the structure. A controlled loop that doubles each existing score can run; a removal loop like ours cannot rely on the live iterator. We now have two design choices. If other code holds a reference to this exact dictionary and should see entries removed, iterate over a separate list of the original keys.
- 02:06
If the caller expects the input unchanged, create a new dictionary instead. These alternatives can print the same filtered pairs while having different side effects. Here the for loop reads a list of keys made before the loop begins. That list is a separate object and its length does not change when we delete a key from scores. Each original name is visited once, and the deletion affects the original dictionary. The result contains Ada and Cy. This is the right shape when changing the original dictionary is intentional. We should make that side effect explicit in the function name or documentation if the code becomes a reusable helper.
- 02:47
There is a memory cost to the key list, but for ordinary small dictionaries it is simple and clear. Do not replace this with `for name in scores.keys()`; that returns a live view and has the same structural issue. You could also copy the dictionary and iterate over the copy, but when only the keys are needed a key list is direct. The important separation is that the loop's iterable stays stable while the target dictionary changes. Now let us compare this with the version that keeps the caller's dictionary untouched. The comprehension visits the original key-value pairs and builds a different dictionary from the entries meeting the threshold.
- 03:27
It does not delete anything from the original while iterating. The first output has Ada and Cy, just like the in-place version, but the next line confirms that the original still contains Bo. This matters when a caller keeps using the input after the function returns. We also test an empty dictionary: the result is an empty dictionary without any special branch. A good test should assert both the returned contents and the input side effect. If we only tested the result, an implementation that deletes from the caller's data might pass unnoticed. For a larger pipeline, returning a new dictionary often makes the function easier to reason about, although it allocates another dictionary.
- 04:12
If another part of the program deliberately relies on shared mutation, the earlier key-snapshot pattern is appropriate. The rule is to choose the intended data contract first, then use a safe iteration pattern that implements it. Your practice task returns a new dictionary containing only scores at least the minimum. It must leave the input unchanged. Use a dictionary comprehension over items, and test a mixed table, a threshold above every score, and an empty table. The automatic checker also verifies that the returned object is not the original dictionary. After passing, try the in-place version with a list of keys and compare the caller-visible behavior.
- 04:56
When you encounter the message dictionary changed size during iteration, locate the loop over the dictionary and the statement adding or removing a key. Remember that keys and items views are live. Catching the exception after the loop breaks is not a repair; separate the thing you iterate over from the structure you modify. The guide includes runnable examples and links to Dictionaries, Loops, and Dictionary Comprehensions. Pause at each run and ask two questions: which entries remain, and did the original dictionary change? Those answers tell you whether the code is both safe and correct for the application.
Delete a low score during a live iteration
A score dictionary contains several players. The program wants to remove scores below 60, so it loops over the dictionary and deletes a matching key. Run the example and inspect the last traceback line. Python raises RuntimeError: dictionary changed size during iteration. The first deletion may already have happened, which means the original dictionary can be partly changed when the error arrives.
The exception is tied to changing the set of keys while the loop uses a live dictionary iterator. It does not mean every operation on a dictionary inside a loop is forbidden. Updating a value for an existing key is a different operation from adding or removing a key. The first debugging step is to find the statement that changes the dictionary's size, often del or pop, inside the loop body.
scores = {'Ada': 92, 'Bo': 48, 'Cy': 75}
for name in scores:
if scores[name] < 60:
del scores[name]RuntimeError: dictionary changed size during iterationSeparate key changes from value changes
A for loop over a dictionary walks its keys. When a key is deleted or inserted, the live iterator no longer has the same dictionary structure it started with. Python detects that size change and stops. The error can appear after the mutating line, when the loop asks the iterator for its next key. That timing explains why printing just before the deletion may look normal while the next step fails.
Changing only the value attached to an existing key does not change the number of keys. In this controlled example, doubling each score works, but using that fact as permission for complicated mutations makes code hard to reason about. If the goal is removal, explicitly choose whether to modify the original dictionary or produce a new filtered result. Also remember that `scores.keys()` and `scores.items()` are live views, not independent copies.
scores = {'Ada': 10, 'Bo': 20}
for name in scores:
scores[name] *= 2
print(scores){'Ada': 20, 'Bo': 40}scores = {'Ada': 92, 'Bo': 48}
keys_view = scores.keys()
print(list(keys_view))
del scores['Bo']
print(list(keys_view))['Ada', 'Bo']
['Ada']Iterate over a snapshot or build a new result
For an intentional in-place change, make a separate list of keys with `list(scores)`. The loop then reads that fixed list while deletions affect the original dictionary. Since the list was created before any deletion, each original key is visited once. This is a simple repair when other code holds a reference to the same dictionary and should observe the changes.
If the input should remain unchanged, a dictionary comprehension is clearer. Iterate through the original key-value pairs and build a new dictionary containing only scores at or above the threshold. This avoids mutating the source at all. Do not copy blindly just to suppress the error; choose the version whose side effects match the function's contract. The two examples print the same filtered values but have different behavior for a caller holding the original object.
scores = {'Ada': 92, 'Bo': 48, 'Cy': 75}
for name in list(scores):
if scores[name] < 60:
del scores[name]
print(scores){'Ada': 92, 'Cy': 75}scores = {'Ada': 92, 'Bo': 48, 'Cy': 75}
passing = {name: score for name, score in scores.items() if score >= 60}
print(passing)
print(scores){'Ada': 92, 'Cy': 75}
{'Ada': 92, 'Bo': 48, 'Cy': 75}Check filtering and the input side effect
Test mixed scores, a dictionary where every score fails, and an empty dictionary. A correct filtering function should return exactly the entries that meet the threshold. If the chosen contract promises a new dictionary, assert that the original remains unchanged too. Without that second check, a function could return the right output while unexpectedly deleting data owned by its caller.
Avoid relying on key order when the task is about membership and values, even though current Python dictionaries preserve insertion order. Compare dictionaries by equality in the exercise. For a more complex loop that adds entries while traversing, build a separate output rather than trying to outsmart the iterator. The broader debugging habit is to separate what you are reading from what you are structurally modifying.
def passing_scores(scores, minimum):
return {name: score for name, score in scores.items() if score >= minimum}
original = {'Ada': 92, 'Bo': 48, 'Cy': 75}
print(passing_scores(original, 60))
print(original == {'Ada': 92, 'Bo': 48, 'Cy': 75})
print(passing_scores({}, 60)){'Ada': 92, 'Cy': 75}
True
{}- Adding or deleting keys changes the dictionary structure during a live iteration.
- Use `list(dictionary)` when the original must be modified in place.
- Build a new dictionary when the input should remain unchanged.
Practice the fix
Write passing_scores(scores, minimum) to return a new dictionary with scores at least minimum. Do not modify scores. Test mixed scores, a threshold higher than every score, and an empty dictionary.
Need a hint?
Use a dictionary comprehension over scores.items(), keeping a pair only when score >= minimum. A new dictionary leaves the input untouched.
See the failure, fix it, then test the boundary
Load each step into the console below and select Run code. Your draft can be restored after trying a step. Loading a sample does not complete the practice task.
- 1 · Reproduce the problem
Expected: RuntimeError: dictionary changed size during iteration
- 2 · Run the repair
Expected: {'Ada': 92, 'Cy': 75}
- 3 · Test an edge case
Expected: The filter works for mixed, empty, and no-match inputs without mutating the original.
- The loop's dictionary iterator expects the number of keys to stay unchanged.
- If you need to delete from the original, iterate over list(scores), a snapshot of the keys.
- If the caller's dictionary must stay unchanged, build a new dictionary from scores.items() instead.
Need the concept first? Study Dictionaries and its checked task
Python console
Ready to runEdit the code and run it in your browser. Examples above can be loaded with Try this example.
Does your code use input()? Add one value per line
Your output appears here.
Write your solution, then select Check practice.
Key takeaways
- A dictionary iterator detects added or deleted keys and raises RuntimeError.
- A list of original keys supports deliberate in-place deletion.
- A comprehension creates a filtered dictionary without mutating the source.
- Verify both the output and whether the original dictionary changed.