Python list.remove(): Syntax, Edge Cases, and Alternatives
Learn how Python's list.remove() works, how it handles missing values and duplicates, and when to prefer pop(), del, or a list comprehension.
Python's list.remove() method deletes the first occurrence of a given value from a list. It mutates the original list and returns None, so it is best for removing a value you already know is present rather than for retrieving the removed element.
fruits = ['apple', 'banana', 'cherry', 'banana'] fruits.remove('banana') print(fruits) # ['apple', 'cherry', 'banana']
If the value appears more than once, only the first occurrence is removed; the remaining duplicates stay in place.
How remove() Locates the Value
remove() scans the list from index zero until it finds an element that compares equal to the argument. In typical Python implementations, this is a linear scan. Equality follows Python's normal == behavior, so custom objects can define what equal means for removal purposes through __eq__. Once a match is found, the element is deleted and the scan stops.
The scan is proportional to the position of the first match, and the shift after deletion is proportional to the number of elements after it. The combined worst-case cost is O(n). Removing an element near the start of a large list takes less scanning, while removing an element near the end requires scanning the entire list.
What Happens When the Value Is Not Found
If no element compares equal to the argument, remove() raises a ValueError. This is the most common failure mode when working with this method:
prices = [10, 20, 30] prices.remove(25) # ValueError: list.remove(x): x not in list
The error propagates immediately, so any code following the call is skipped. When the value's presence is uncertain, check membership first or catch the exception:
if 25 in prices: prices.remove(25)
The membership check adds its own O(n) scan, so when the value is present the combined cost can be as high as two full passes over the list. For a one-off removal that is usually acceptable. If the value is usually present, catching the ValueError avoids the extra membership scan but makes the control flow slightly less explicit. If the value is usually absent, checking membership first is clearer and avoids relying on exception handling.
Removing Every Occurrence Instead of the First
Because remove() deletes only the first match, it cannot be used directly to clear all duplicates of a value. Calling it repeatedly until the value disappears works but is inefficient, since each call scans the list from the beginning again. A list comprehension produces a new list containing only the elements that should stay:
prices = [10, 20, 10, 30, 10] prices = [p for p in prices if p != 10] print(prices) # [20, 30]
This builds a new list rather than mutating the original. If other references point to the original list, they will not see the change. When in-place mutation is required, an assignment to a slice achieves the same result while preserving the original list object:
prices[:] = [p for p in prices if p != 10]
The slice assignment keeps prices as the same object, which matters when the list is shared through multiple variables or stored in a container.
Comparing remove(), pop(), and del
The three main removal tools in Python cover different needs. remove() targets a value; pop() targets an index and returns the removed element; del targets an index or slice without returning anything.
| Method or statement | Removes by | Returns value | Mutates in place |
|---|---|---|---|
remove(x) | value | None | yes |
pop(i) | index | removed element | yes |
del lst[i] | index or slice | nothing | yes |
Use pop() when the index is known and the removed value is needed for further processing, such as implementing a stack. Use del when removing a slice or when the index is known and the value is irrelevant. Use remove() when only the value is known and the index is not available.
Performance and Memory Behavior
Every removal from the middle of a list shifts the remaining elements, so repeated removals from the same list have cumulative O(n) cost per removal. For a loop that removes many elements by value, the total cost becomes O(n^2). In that scenario, building a filtered list with a comprehension is O(n) and usually the better choice.
The tradeoff is memory. A list comprehension allocates a new list while the original list still exists, so peak memory usage can be substantially higher during the operation. remove() works in place and does not allocate a second list, but pays the shifting cost on every call. For large lists where memory is constrained and only a few elements are removed, remove() is reasonable. For bulk filtering, the comprehension wins on time.
Safe Removal While Iterating
Modifying a list while iterating over it with a for loop leads to skipped elements, because the iterator tracks the current index while the list shrinks underneath it:
values = [1, 2, 3, 4] for v in values: if v % 2 == 0: values.remove(v)
This loop skips the value 3 and leaves the list in an unexpected state. Iterating over a copy of the list avoids the problem:
for v in values[:]: if v % 2 == 0: values.remove(v)
The slice values[:] creates a snapshot, so the loop iterates over the original contents while remove() mutates the live list. For anything more than a handful of removals, a list comprehension is simpler and avoids the entire class of iteration bugs.
Choosing the Right Removal Approach
The decision depends on whether the original list object must be preserved, whether the removed value is needed, and how many removals are required. remove() is the right tool for a single removal by value when the value is known to exist. pop() is right when the index is known and the value is needed. A list comprehension is right when multiple elements must be filtered out and a new list is acceptable. Slice assignment with a comprehension is right when the list must be filtered in place while preserving the object identity.
The most common mistake is reaching for remove() inside a loop over the same list. That pattern is both slow and error-prone. Recognizing when the operation is a single removal versus a bulk filter is the key to writing correct, efficient list mutation code.