Skip to main content

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.
warning

Never modify a list while iterating over it directly using a for loop and remove(), pop(), insert(), del, or other in-place /modification methods.

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 through my_list, and only includes item in the new list if item >= 50. The original my_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 the list() 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 a deepcopy 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 the range 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 the check_func returns False.