Why does removing items from a list while looping over it skip elements?
Short answer
Removing an item shifts everything after it back by one, but the loop's counter keeps advancing, so the next item is skipped. Build a new list with a comprehension instead of mutating while iterating.
The short version
When you remove an item, every later item shifts back one position, but the loop's internal counter still moves forward. The result is that the item immediately after each removal is skipped. Do not mutate a list while iterating it: build a new list with a comprehension instead.
Seeing the bug
numbers = [1, 2, 2, 3]
for n in numbers:
if n == 2:
numbers.remove(n)
print(numbers) # [1, 2, 3] , one of the twos survived!
The first 2 is removed at position 1, so the second 2 shifts into position 1. But the loop has already moved on to position 2, skipping straight over it. The code looks correct and produces a silently wrong result, which is the worst kind of bug.
The fix: build a new list
numbers = [n for n in numbers if n != 2] # [1, 3]
This creates a new list and never mutates the one being read, so nothing can be skipped. It is also clearer: it states what you want to keep rather than describing a deletion process.
If you must modify in place
When other references to the same list must see the change, assign back into the same object with a slice.
numbers[:] = [n for n in numbers if n != 2]
The [:] replaces the contents of the existing list rather than rebinding the name, so anyone else holding a reference sees the update.
Iterating over a copy
Another safe option is to loop over a copy while modifying the original.
for n in numbers[:]: # iterate the copy
if n == 2:
numbers.remove(n) # modify the original
This works, but the comprehension is clearer and faster, because remove scans the list to find each item.
The same trap with dictionaries
Adding or removing keys during iteration raises RuntimeError: dictionary changed size during iteration. Python is stricter there, so you get a loud error instead of silent skipping. The fix is the same: build a new dictionary.
prices = {k: v for k, v in prices.items() if v > 3}
The general rule
Do not modify a collection while iterating over it. Either build a new one (usually clearest), or iterate over a copy. This applies to lists, dictionaries and sets alike, and the list case is the most dangerous precisely because it fails silently rather than raising.