How to Safely Remove Items from a List While Iterating in Python
Removing elements from a list while iterating over it directly is a common source of bugs in Python.
This guide explains why directly modifying a list during iteration is problematic and presents the safe and correct ways to achieve the desired filtering: using list comprehensions, the filter()
function, iterating over a copy, and iterating in reverse.
The Problem: Modifying a List While Iterating (Don't Do This!)
Consider this (incorrect) code:
my_list = [22, 33, 66, 77, 99]
# ⛔️ INCORRECT: Modifying the list while iterating over it directly
for item in my_list:
if item < 50:
my_list.remove(item)
print(my_list) # Output: [33, 66, 77, 99] - WRONG!
You might expect this to remove all numbers less than 50. However, the output is [33, 66, 77, 99]
. Why?
- When you remove an item, the indices of all subsequent items shift. The loop's internal counter doesn't account for this, so you skip elements.
- In the example above, when the number 22 is removed, all other numbers get shifted to the left, and the loop will not process the element that was previously at index 1, which is 33.
Never modify a list while iterating over it directly using a for
loop and remove()
, pop()
, insert()
, del
, or other in-place /modification methods.
Solution 1: List Comprehensions (Recommended)
List comprehensions are the best way to create a new list containing only the elements you want to keep:
my_list = [22, 33, 66, 77, 99]
new_list = [item for item in my_list if item >= 50] # Keep only items >= 50
print(new_list) # Output: [66, 77, 99]
print(my_list) # Output: [22, 33, 66, 77, 99] (original list unchanged)
[item for item in my_list if item >= 50]
: This creates a new list. It iterates throughmy_list
, and only includesitem
in the new list ifitem >= 50
. The originalmy_list
is not modified.- This is concise, readable, and efficient. It's the most Pythonic way to filter a list.
- If you want to modify the original list, you can reassign it:
my_list = [item for item in my_list if item >= 50]
Solution 2: The filter()
Function
The filter()
function provides another way to create a new list with filtered elements:
my_list = [22, 33, 66, 77, 99]
new_list = list(filter(lambda x: x >= 50, my_list))
print(new_list) # Output: [66, 77, 99]
- The
filter
function takes a function that will be called with the elements from the list. - The returned object will only contain the elements for which the function returned
True
. filter
returns an iterator so we have to use thelist()
constructor to create a new list.
Solution 3: Iterating Over a Copy (Sometimes Useful)
If you must modify the original list in-place, and you can't use a list comprehension for some reason, you can iterate over a copy of the list:
my_list = [22, 33, 66, 77, 99]
for item in my_list.copy(): # Iterate over a *copy*
if item < 50:
my_list.remove(item) # Modify the *original* list
print(my_list) # Output: [66, 77, 99]
-
The
my_list.copy()
returns a copy of the list. -
my_list.copy()
: Creates a shallow copy of the list. This is safe for a list of numbers (or other immutable objects). If your list contains mutable objects (like nested lists or dictionaries), you might need adeepcopy
in some, very specific cases. -
The
remove()
method removes the first matching item.
Solution 4: Iterating in Reverse (In-Place Modification, Specific Cases)
Another way to modify a list in-place while iterating is to iterate in reverse order. This works because removing an element only shifts the indices of elements after the current index:
my_list = [22, 33, 66, 77, 99]
for index in range(len(my_list) - 1, -1, -1): # loop in reversed order
if my_list[index] < 50:
my_list.pop(index)
print(my_list) # Output: [66, 77, 99]
-
The
for
loop uses therange
with a negative step to iterate in reverse order. -
The
pop()
method removes the item at the index. -
range(len(my_list) - 1, -1, -1)
: This creates a sequence of indices from the last element's index down to 0, in reverse order. -
my_list.pop(index)
: Removes the element at the current index. Because we're iterating in reverse, removing an element doesn't affect the indices of the elements we haven't processed yet.
This method is only suitable for in-place modification and is less readable than a list comprehension.
Using filterfalse
You can remove items from a list using filterfalse
from the itertools
module:
from itertools import filterfalse
my_list = [77, 105, 123, 88, 199, 4, 1, 5]
def check_func(num):
return num < 100
my_list[:] = filterfalse(check_func, my_list)
print(my_list) # Output: [105, 123, 199]
- The
filterfalse()
method returns items from the iterable for which thecheck_func
returns False.