2 min read

The Python Pitfall: Why You Shouldn't Modify Lists During Iteration

The Python Pitfall: Why You Shouldn't Modify Lists During Iteration
Photo by Aleksandr Popov / Unsplash

Introduction

Hello fellow Python enthusiasts! Today, we'll delve into a common pitfall that catches even experienced developers off guard: modifying a list while iterating over it. If you've ever found yourself scratching your head over unexpected results after trying to modify a list in a loop, this blog post is for you.

The Unexpected Behavior

Let's start with an example, which you may have seen in a tweet:

numbers = list(range(1, 50))
for i in numbers:
    if i < 20:
        numbers.remove(i)
print(numbers)

Expected Output

You'd expect that all numbers below 20 would be removed from the list, leaving you with a list ranging from 20 to 49.

Actual Output

But what you actually get is a list like this:

[2, 4, 6, 8, 10, 12, 14, 16, 18, 20, ..., 49]

Confused? You're not alone!

The Culprit: List Re-indexing

The reason behind this unexpected behavior is the way Python handles list indexing. When you remove an element from a list, the indices of the elements after the removed item are updated. This results in the loop skipping the next item every time an element is removed.

Breaking it Down

  • When i = 1, the remove() method removes the first element (1), and all elements are shifted left.
  • Now, i = 2, but the loop goes to the element that was originally at index 2, which is now 3.
  • The element 2 is skipped, and this pattern continues.

The Pythonic Solution: List Comprehensions

To avoid this pitfall, you can use list comprehensions, a Pythonic way to generate lists. Here's how you can rewrite the example:

numbers = list(range(1, 50))
numbers = [n for n in numbers if n >= 20]
print(numbers)

Output

As expected, this will output a list ranging from 20 to 49.

[20, 21, ..., 49]

Why List Comprehensions are Awesome

  1. Readability: They make your code more concise and readable.
  2. Performance: List comprehensions are generally faster than traditional for loops, especially for larger datasets.
  3. Pythonic: Using list comprehensions is considered Pythonic, and it's a skill you'll see often in advanced Python code.

Bonus: Other Alternatives

While list comprehensions are great, sometimes they may not fit your needs. Here are some other ways to safely modify a list while iterating:

  1. Using slice() to create a copy:

    for i in numbers[:]:  # iterate over a slice copy of the entire list
        if i < 20:
            numbers.remove(i)
    
  2. Iterating backwards:

    for i in reversed(numbers):
        if i < 20:
            numbers.remove(i)
    
  3. Using filter() function:

    numbers = list(filter(lambda x: x >= 20, numbers))
    

Conclusion

Modifying a list while iterating over it is a common pitfall that can lead to unexpected behavior due to the re-indexing of the list. The Pythonic way to handle such scenarios is to use list comprehensions. They are not only more readable but also generally faster.

Remember, Python is designed to be explicit and readable, and understanding these nuances can help you write more effective and bug-free code.

Happy coding!