How to Solve "RuntimeError: dictionary/set changed size during iteration" in Python
Attempting to add or remove items from a dictionary or set while iterating over it directly in Python leads to a RuntimeError
.
This guide explains why this error occurs and presents several correct ways to modify dictionaries and sets during iteration, including creating copies, using list/dict comprehensions, and iterating over keys instead of the collection itself.
Understanding the RuntimeError
Python's dictionaries and sets are implemented using hash tables. This allows for very fast lookups, insertions, and deletions (typically O(1) time complexity). However, this efficiency comes with a constraint: you can not change the size of the hash table while iterating over it. Doing so would invalidate the iterator and potentially lead to incorrect results or crashes. Hence the RuntimeError
.
Example (Dictionary - Incorrect):
my_dict = {'a': 1, 'b': 2, 'c': 3}
# ⛔️ RuntimeError: dictionary changed size during iteration
# for key in my_dict:
# print(key)
# if key == 'b':
# del my_dict[key]
Example (Set - Incorrect):
my_set = {'Alice', 'Bob', 'Carl'}
# ⛔️ RuntimeError: Set changed size during iteration
# for i in my_set:
# print(i)
# if i == 'Bob':
# my_set.remove(i)
Solutions for Dictionaries
Iterating Over a Copy (Recommended for Dictionaries)
The simplest and often most efficient way to avoid the error is to iterate over a copy of the dictionary's keys, values, or items:
my_dict = {'a': 1, 'b': 2, 'c': 3}
for key in my_dict.copy(): # Iterate over a *copy* of the keys
print(key)
if key == 'b':
del my_dict[key] # Modify the *original* dictionary
print(my_dict) # Output: {'a': 1, 'c': 3}
my_dict.copy()
: Creates a shallow copy of the dictionary. This means you have a new dictionary object, but the values within the dictionary are still references to the same objects. For most use cases with simple values (numbers, strings), this is sufficient. If you have nested dictionaries/lists and need to modify those nested structures during iteration, you'll need adeepcopy
(from thecopy
module).- You can also convert the dictionary into a list of keys by using
list(my_dict.keys())
and iterate over it.
Iterating Over a List of Keys
Another way to avoid modifying the dictionary while iterating it is to iterate over a list of its keys, and use those to access and modify its content:
my_dict = {'a': 1, 'b': 2, 'c': 3}
for key in list(my_dict.keys()): # Iterate over the keys
print(key)
if key == 'b':
del my_dict[key] # Deleting a key by value
print(my_dict) # Output: {'a': 1, 'c': 3}
list(my_dict.keys())
creates a copy of the dictionary's keys as a list. You are now iterating over this list, not the dictionary itself, so modifying the dictionary is safe.- You can also use
list(my_dict.items())
if you need both the key and value.
Using Dictionary Comprehension (Creating a New Dictionary)
If you want to create a new dictionary based on filtering or transforming the original, a dictionary comprehension is a concise and efficient solution:
my_dict = {'a': 1, 'b': 2, 'c': 3}
keys_to_remove = ['a', 'b']
my_dict = {key: value for key, value in my_dict.items()
if key not in keys_to_remove} # Creates a new dictionary
print(my_dict) # Output: {'c': 3}
- This creates a new dictionary, including only the key-value pairs where the key is not in
keys_to_remove
. The originalmy_dict
remains unchanged.
Using Two for
Loops
Another possible (but less efficient) method is to use two separate for loops:
my_dict = {'a': 0, 'b': 1, 'c': 0}
keys_to_remove = []
for key, value in my_dict.items(): # First loop: Collect keys
if not value: # Check condition
keys_to_remove.append(key) # Add to the remove list
for key in keys_to_remove: # Second loop: Remove the keys
del my_dict[key]
print(my_dict) # Output: {'b': 1}
- The first for loop is used to iterate over the original dictionary to collect the keys that need to be removed.
- The second for loop will then iterate over the
keys_to_remove
list and remove each element from the dictionary.
Solutions for Sets
Iterating Over a Copy (Recommended for Sets)
The same principle applies to sets: iterate over a copy to avoid modifying the set during iteration:
my_set = {'Alice', 'Bob', 'Carl'}
for i in my_set.copy(): # Iterate over a *copy*
print(i)
if i == 'Bob':
my_set.remove(i) # Modify the *original* set
print(my_set) # Output: {'Alice', 'Carl'}
Using a List Comprehension (Creating a New Set)
Similar to dictionaries, you can create a new set using a set comprehension (or a list comprehension and then converting to a set):
my_set = {'Alice', 'Bob', 'Carl'}
my_new_set = set([i for i in my_set if i != 'Bob']) # Create a new set
print(my_new_set) # Output: {'Alice', 'Carl'}
Using Two for
Loops
my_set = {'Alice', 'Bob', 'Carl', 'Bobby'}
elements_to_remove = []
for element in my_set: # First loop: Collect keys
if 'Bob' in element: # Check if the item matches the criteria
elements_to_remove.append(element) # Append to list
for element in elements_to_remove:
my_set.remove(element) # Remove elements
print(my_set) # Output: {'Carl', 'Alice'}
Conclusion
The RuntimeError: dictionary/set changed size during iteration
error is a safeguard against unexpected behavior. The key is to never modify a dictionary or set directly while iterating over it.
Instead, iterate over a copy (using .copy()
, list(my_dict.keys())
, etc.), use a list/dict comprehension to create a new collection, or collect items to remove in a separate list and then remove them in a second loop.
The best approach depends on whether you need to modify the original collection in-place or create a new one.
By following these guidelines, you'll avoid this common error and write more robust Python code.