Python List remove() vs pop(): Delete by Value or by Index
When you want one item out of a Python list, you've got two methods to pick from, and what separates them is what you know about the item going in. The remove() method finds it by its value, while pop() takes it by position and hands it back to you:
queue = ["Ada", "Bo", "Cleo", "Dev"]
queue.remove("Bo")
served = queue.pop(0)
print(served)
print(queue) # Program output:
# Ada
# ['Cleo', 'Dev'] The first call went looking for "Bo" by value and deleted it. The second call didn't care what sat at index 0, it just pulled out "Ada" and gave it back so you could keep it in served.
Keep that split between value and position in mind, because several of the traps below come straight out of it.
How Does Python remove() Delete a List Item?
Python's list.remove() method starts at the front of the list and deletes the first item that matches the value you pass. It changes the list in place, so there's no new list for you to catch.
list.remove(x)
# deletes the first matching item and returns None - x (required, any type): the value to look for.
- Returns:
None. The list itself is what changes. - Raises:
ValueErrorwhen the search reaches the end of the list without a match.
For each item, Python first asks whether it's the very same object you passed, and if it isn't, whether the two compare equal with ==. In everyday code, the first item equal to x goes, even if it's a different object. The NaN pitfall below is where that first step changes the result.
tags = ["python", "draft", "lists", "draft"]
tags.remove("draft")
print(tags) # Program output:
# ['python', 'lists', 'draft'] Only the first "draft" got deleted. The second copy is still sitting right there at the end, and that surprises plenty of people who figured remove() would clear out every copy. When you need them all gone, the Removing Every Match example shows you how.
How Does Python pop() Remove and Return a List Item?
Python's list.pop() removes an item by position and returns that item. You give it an index, and it pulls out whatever sits there and returns it, so the value is still yours to use after it leaves the list.
items.pop() # last item
items.pop(i) # item at index i - i (optional integer): the position to remove. It defaults to
-1, the last item, and negative indexes count back from the end. Pass a whole number, and pass it positionally: bothpop("2")andpop(index=0)raiseTypeError. - Returns: the removed item.
- Raises:
IndexErrorwhen the list is empty or the index is out of range.
history = ["open", "edit", "format", "save"]
last = history.pop()
first = history.pop(0)
second_last = history.pop(-2)
print(last, first, second_last)
print(history) # Program output:
# save open edit
# ['format'] Pay attention to how the positions move between calls. After the first two pops the list is ["edit", "format"], so by then -2 points at "edit", even though it pointed at "format" when you started. Each pop can change what an index points at, so always read an index against the list as it stands right now.
Why Does Python remove() Return None?
Python remove() returns None because its job is to change the list, not return the deleted item. Methods such as append() and sort() follow the same convention. Use pop() when you need to keep the removed item.
cart = ["milk", "eggs", "bread"]
result = cart.remove("eggs")
print(result)
print(cart) # Program output:
# None
# ['milk', 'bread'] So if you write cart = cart.remove("eggs"), you've thrown away your list and kept the None. Python won't say a word on that line, and the error only shows up when a later line tries to use cart as a list, which sends you hunting for the bug in the wrong place.
If you are still learning conditions and loops, the Python basics guide gives you a starting point.
Practical Examples
An Undo Stack
Pair append() with a bare pop() and a list turns into a stack, where the last thing in is the first thing out. Picture the plate stack at a diner: the last plate the dishwasher sets on top is the first one the next server grabs. An editor's undo history works the same way:
undo_stack = []
undo_stack.append("typed 'Hello'")
undo_stack.append("made text bold")
undo_stack.append("deleted a line")
while undo_stack:
action = undo_stack.pop()
print("undo:", action) # Program output:
# undo: deleted a line
# undo: made text bold
# undo: typed 'Hello' The while undo_stack: test is what keeps this loop safe. An empty list counts as false, so the loop quits the moment the stack runs out and pop() never gets called on an empty list.
Removing Every Match
Because remove() quits after the first match, clearing out every copy of a value takes a different tool. My pick is a list comprehension that keeps everything except the value you're getting rid of:
tags = ["draft", "python", "draft", "lists"]
tags = [tag for tag in tags if tag != "draft"]
print(tags) # Program output:
# ['python', 'lists'] That line builds a brand-new list and points the name tags at it. If another variable still points at the old list and has to see the change too, assign into a full slice with tags[:] = [...], which swaps out the contents of that same list object. The comprehensions guide covers the filtering syntax in full.
Common Pitfalls and Debugging
ValueError When the Value Is Missing
Hand remove() a value the list doesn't hold and you get a ValueError. CPython, the standard interpreter, prints it as ValueError: list.remove(x): x not in list, which is the exact line to paste into a search bar if you ever forget what it means. When the value might legitimately be missing, check for it first with in:
tags = ["python", "lists"]
if "draft" in tags:
tags.remove("draft")
print(tags) # Program output:
# ['python', 'lists'] When the value is usually there, the in test and remove() both search from the front until they reach it, so you pay for that search twice. A try block skips the separate check and only catches the error when the value turns out to be missing:
tags = ["python", "draft", "lists"]
try:
tags.remove("draft")
except ValueError:
print("no draft tag to remove")
print(tags) # Program output:
# ['python', 'lists'] Both versions handle a missing value correctly, and on a short list the extra search rarely matters. I'd pick whichever one reads better in that spot and not lose any sleep over it.
IndexError: Pop from Empty List
Call a bare pop() on an empty list and you get an IndexError, and an index past either end gets you the same error type. CPython prints IndexError: pop from empty list for the first case and IndexError: pop index out of range for the second. Because an empty list counts as false, the guard is short:
jobs = []
if jobs:
next_job = jobs.pop()
else:
print("queue is empty") # Program output:
# queue is empty Removing While You Loop
Deleting from a list while a for loop walks over it can make the loop skip items. Each deletion slides every later item down one slot, right underneath the loop, and the loop carries on counting as if nothing moved:
numbers = [2, 4, 5, 6]
for number in numbers:
if number % 2 == 0:
numbers.remove(number)
print(numbers) # Program output:
# [4, 5] The 4 survived because it slid into a slot the loop had already finished with. No single line in that code is wrong on its own, which is exactly why this bug is so annoying to track down.
Build a filtered list with a comprehension instead, as in the example above, or loop over a copy with for number in numbers[:]:. The Python Lists guide walks through the bug step by step.
Equal Values Count as a Match
An equal value counts as a match whatever its type, because remove() never checks types itself. In Python True == 1 and 1 == 1.0 are both true, and that catches people out in mixed lists:
answers = [True, 1, 1.0]
answers.remove(1)
print(answers) # Program output:
# [1, 1.0] You asked for the integer and lost the boolean at index 0 instead. Don't bother reaching for index() to fix it, because index() finds items exactly the same way remove() does. When the type matters, find the position yourself with enumerate() and a type check, then pop it:
answers = [True, 1, 1.0]
for position, answer in enumerate(answers):
if type(answer) is int and answer == 1:
answers.pop(position)
break
print(answers) # Program output:
# [True, 1.0] The break ends the loop straight after the pop, so the items shifting down never get the chance to confuse it.
NaN is the odd case that runs the other way. A float NaN isn't equal to any built-in number, itself included, so passing a fresh float("nan") to remove() raises ValueError even when a NaN is sitting right there in a list of ordinary numbers.
Pass the exact NaN object that's sitting in the list, though, and remove() finds it. That's the same-object step from Deleting by Value doing its job, and in checks a list the same way.
Popping from the Front of a Long List
Popping from the end is fast at any size. Popping from index 0 forces every remaining item to shift down one place, so a queue that keeps calling pop(0) gets slower as it grows.
Think of a deli counter where the whole line takes a step forward every time a number gets called, which nobody minds with four people waiting and everybody minds with a line out the door. The Python tutorial points you to collections.deque for this job:
from collections import deque
line = deque(["Ada", "Bo", "Cleo"])
line.append("Dev")
print(line.popleft())
print(line) # Program output:
# Ada
# deque(['Bo', 'Cleo', 'Dev']) For a short list the difference rarely matters, so don't go rewriting working code over it. Reach for deque when the queue is long or the loop runs often.
Remove, Pop, Del, and Clear Compared
You've got four common ways to delete from a list, and they split on two things: what each one targets, and what it hands back to you.
| Tool | Deletes | Returns | Error |
|---|---|---|---|
remove(x) | First matching item | None | ValueError if absent |
pop(i) | One index, last by default | The item | IndexError if out of range |
del items[i] | One index or a slice | Nothing | IndexError on a bad single index |
clear() | Every item | None | Nothing to catch |
The one to remember for ranges is the del statement, because it's the only one of the four that takes a slice:
scores = [90, 72, 85, 64, 99]
del scores[1:3]
print(scores)
scores.clear()
print(scores) # Program output:
# [90, 64, 99]
# [] Choose a Removal Method by Value or Position
When you know the value, use remove(), and when you know the position or need the item back afterwards, use pop(). For a range, reach for del with a slice.
For every copy of a value, skip remove() altogether and rebuild the list with a comprehension, since calling it inside a for loop over the same list is how the skipped-item bug from earlier happens.
Frequently Asked Questions
Does Python remove() delete every matching list item?
Python list.remove() deletes only the first matching item, leaving any later copies in the list. To delete every copy, build a new list with a comprehension that keeps only the items you want.
What does Python list.pop() return without an index?
Python list.pop() removes and returns the last item when you omit the index. You can save that item in a variable, as in last = items.pop(). Calling pop() on an empty list raises IndexError.
Is Python pop() faster than remove() on a long list?
Popping the last item from a Python list is generally faster than searching for a value with remove(). Removing near the front takes more work because later items must shift. Calling pop(0) has that shifting cost too.
How do you delete a Python list item by index without returning it?
To delete a Python list item by index without returning it, use del items[2]. The del statement also accepts a slice, so del items[1:3] deletes a range. Neither remove() nor pop() can delete a range in one call.
Can Python list.pop() take a default value?
Python list.pop() does not accept a default value. It accepts one optional index and raises IndexError if that position does not exist. Dictionary pop() is different: dict.pop(key, default) returns the default when the key is missing.
Does Python pop() work on tuples and strings?
Python tuples and strings have no pop() or remove() method because they cannot change in place. Calling either method raises AttributeError. Build a new tuple or string without the unwanted item, or convert to a list first.
Next Steps
- Python List insert() - adding one item at a position you choose.
- Python Dictionaries - where pop() takes a key and a default instead of an index.
- Lists vs Dictionaries vs Sets in Python - when a set is the better home for values you delete by name.
Sources
-
[1]
Data Structures: More on Lists(docs.python.org)
-
[2]
Mutable Sequence Types(docs.python.org)
-
[3]
Object Protocol: PyObject_RichCompareBool(docs.python.org)
-
[4]
Data Structures: Using Lists as Queues(docs.python.org)
-
[5]
collections: deque objects(docs.python.org)
-
[6]
Simple statements: The del statement(docs.python.org)
Read Next
A Python list keeps items in the order you put them and lets you change that order later. How to create one, index and slice it, add and remove items, copy it safely, and recognize the jobs a list is wrong for.
A comprehension builds a list or a dictionary from an existing iterable in one expression. The syntax for both forms, filtering, conditional values, nesting, and the cases where a plain loop is the better answer.
A Python dictionary stores values under names you choose instead of numbered positions. Creating one, reading keys safely, updating and merging, looping over views, counting and grouping, and the JSON boundary.