DEV Community

Samuel Ochaba
Samuel Ochaba

Posted on

The Python Loop Bug That Causes Silent Data Loss

Here's a bug that's particularly nasty because it sometimes works correctly:

numbers = [1, 2, 3, 4, 5, 6]
for num in numbers:
    if num % 2 == 0:
        numbers.remove(num)

print(numbers)
# Expected: [1, 3, 5]
# Actual:   [1, 3, 5, 6]
Enter fullscreen mode Exit fullscreen mode

The goal was to remove all even numbers. But 6 survived.

Why This Happens

When you call remove() on a list, every item after the removed element shifts down by one index. The list gets shorter.

But the for loop doesn't know this happened. It maintains an internal counter that increments regardless. After processing index 1, it moves to index 2—even though what was at index 2 is now at index 1.

Let's trace through:

Initial: [1, 2, 3, 4, 5, 6]

Index 0: num=1, odd, keep it
Index 1: num=2, even, remove it → [1, 3, 4, 5, 6]
Index 2: num=4 (3 got skipped!), remove it → [1, 3, 5, 6]
Index 3: num=6 (5 got skipped!), now at end of (shorter) list
Enter fullscreen mode Exit fullscreen mode

The bug is intermittent. Sometimes it appears to work. Sometimes it loses data. The worst kind of bug.

The Fixes

1. Iterate over a copy

for num in numbers[:]:  # [:] creates a shallow copy
    if num % 2 == 0:
        numbers.remove(num)
Enter fullscreen mode Exit fullscreen mode

The loop iterates over the copy (which doesn't change) while you modify the original.

2. Build a new list (better)

numbers = [num for num in numbers if num % 2 != 0]
Enter fullscreen mode Exit fullscreen mode

This is the Pythonic approach. No mutation during iteration. No subtle bugs. List comprehensions are also faster than repeated remove() calls.

The Rule

Never modify a collection you're actively iterating over:

  • Don't remove() items from a list during a for loop over that list
  • Don't del keys from a dict while iterating over that dict
  • Don't add() to a set while iterating over that set

Either iterate over a copy, or build a new collection with the items you want to keep.

Quick Reference

# DANGEROUS
for item in items:
    items.remove(item)  # Skips elements!

# SAFE: Copy
for item in items[:]:
    items.remove(item)

# SAFE: New list (preferred for filtering)
items = [x for x in items if keep(x)]
Enter fullscreen mode Exit fullscreen mode

From my upcoming book "Zero to AI Engineer: Python Foundations."

Top comments (0)