Iterating a list while removing its elements can skip values. Compute the kept elements first; use slice assignment if existing references must observe the replacement.
Check the failure with a small fixture
Adjacent matching items survive a cleanup loop because removal shifts the remaining indexes.
values = [0, 0, 1, 0]
for item in values:
if item == 0:
values.remove(item)
assert values == [1, 0]
values = [0, 0, 1, 0]
alias = values
values[:] = [item for item in values if item != 0]
assert values == [1] and alias is values
original = [0, 2, 0]
filtered = [x for x in original if x != 0]
assert original == [0, 2, 0] and filtered == [2]What the result means
The loop advances its position after each step. Removing an item shifts later items left, so the next value can move into an index already passed by the iterator. The demonstration deliberately includes adjacent matches; a fixture with alternating values can conceal the defect.
Choose the correction for your caller
Use a comprehension when the rule is a predicate over each original item. Rebinding a variable to the result leaves other references pointing at the previous list; assigning to values[:] retains the list object. If deletion depends on positions, collect indexes first and delete in descending order. Avoid removing by value when duplicate equal objects need different treatment: remove deletes the first equal item, which might not be the occurrence you intended. If every original item needs a separate action, traverse a snapshot and perform that action once per original occurrence while collecting survivors. Do not move the action into a predicate unless its order and failure behavior are part of the intended contract.
Limits and the next check
The examples perform no external side effects. If a predicate modifies other data, decide what should happen when it raises before committing a replacement. Filtering makes a new outer list; it does not deep-copy retained elements.
Related: Compare a related data or execution contract.
The embedded Python example was executed as an isolated local script on November 10, 2026 (KST), using Python 3.11.6. It demonstrates the stated boundary; it is not a test of your application or a promise of compatibility with every Python release.