Skip to main content

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

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 a deepcopy (from the copy 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 original my_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

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.