Eleven methods cover nearly everything you will ever do to a list: add items, remove them, find them, sort them, copy the lot. Knowing which one fits saves you from writing a five-line workaround for a one-line job. This post explains all the Python list methods with tested examples, grouped by what they do rather than by the alphabet.
“There’s an easy way and a hard way. The easy way is always harder to find.”
Raymond Hettinger, PyCon talks
Last Updated: July 2026 | Tested on: Python 3.14.6 | Difficulty: Beginner | Reading Time: 13 minutes
A list is only as useful as the things you can do with it. Think of a list method like a tool in a toolbox. Some tools add stuff to the list (append(), extend(), insert()). Some take stuff out (remove(), pop(), clear()). Some help you find things (index(), count()), and some rearrange the order (sort(), reverse()). One last tool makes a safe duplicate (copy()). Pick the right tool and the job takes one line.
There is one habit that trips up almost every beginner, so let us get it out of the way early. Most of these methods change the list right where it sits and hand you back None instead of the new list. So my_list.sort() sorts the list but gives you nothing to assign. Knowing which methods return a real value and which return None saves you from the single most common list bug in Python.
Python lists have 11 built-in methods. You will reach for about 6 of them all the time and the rest now and then. Here they all are, grouped by what they do, with a tested example and the real output for each. Bookmark this page and come back whenever you forget how one of them behaves.
Table of Contents
Python List Methods: Quick Reference
Tip: click the diagram to open it full screen, then use the zoom and pan controls for a closer look.
The diagram sorts all 11 methods into five groups: adding elements, removing elements, finding and counting, ordering, and copying. That mirrors how you actually use a list day to day. You are either building it up, trimming it down, looking something up, reordering it, or making a safe copy. Come back to this map whenever you blank on whether the method you want is remove() (which deletes by value) or pop() (which deletes by index).
| Method | What It Does | Returns | Mutates? |
|---|---|---|---|
| append(x) | Add x to end | None | Yes |
| extend(iter) | Add all items from iterable | None | Yes |
| insert(i, x) | Insert x before index i | None | Yes |
| remove(x) | Remove first occurrence of x | None | Yes |
| pop([i]) | Remove & return item at i | The item | Yes |
| clear() | Remove all items | None | Yes |
| index(x) | Find first index of x | int | No |
| count(x) | Count occurrences of x | int | No |
| sort() | Sort in place | None | Yes |
| reverse() | Reverse in place | None | Yes |
| copy() | Shallow copy | New list | No |
Adding Elements
Say you are keeping the member list for a small coding club. Two members, Rahul and Niranjan, signed up first. New people join in different ways: one at a time, as a whole batch, or someone important who needs a specific spot on the list. That is exactly the split between append(), extend(), and insert().
📄 adding.py: append, extend, insert
team = ["Rahul", "Niranjan"]
# append: add ONE item to the end
team.append("Viraj")
print(f"After append: {team}")
# extend: add ALL items from another iterable
team.extend(["Anvi", "Aditi"])
print(f"After extend: {team}")
# insert: add at a specific position
team.insert(1, "Prathamesh")
print(f"After insert at 1: {team}")
# careful: append vs extend with a list
test = [1, 2]
test.append([3, 4]) # adds the LIST as one element
print(f"append list: {test}") # [1, 2, [3, 4]]
test2 = [1, 2]
test2.extend([3, 4]) # adds EACH item
print(f"extend list: {test2}") # [1, 2, 3, 4]
▶ Output
After append: ['Rahul', 'Niranjan', 'Viraj'] After extend: ['Rahul', 'Niranjan', 'Viraj', 'Anvi', 'Aditi'] After insert at 1: ['Rahul', 'Prathamesh', 'Niranjan', 'Viraj', 'Anvi', 'Aditi'] append list: [1, 2, [3, 4]] extend list: [1, 2, 3, 4]
What happened here: the difference between append and extend catches everyone once. Picture a grocery bag. append drops in whatever you hand it as a single item, so handing it a small bag of apples puts that whole bag inside, which is why you got the nested [3, 4]. extend instead empties the bag and adds each apple on its own, giving you a flat [1, 2, 3, 4]. And insert(1, "Prathamesh") slides the name into slot 1 and shoves everyone after it one spot to the right.
Removing Elements
Removing works like a shopping list stuck on the fridge. Sometimes you strike off an item by name (that is remove()). Sometimes you tear off the last entry and take it with you to the shop (that is pop(), which hands the item back to you). And when the shopping is done, you wipe the whole list clean (clear()).
📄 removing.py: remove, pop, clear, del
scores = [88, 72, 95, 72, 61, 84]
# remove: delete the FIRST occurrence of a value
scores.remove(72)
print(f"After remove(72): {scores}") # only first 72 gone
# pop: remove and RETURN by index (default is the last item)
last = scores.pop()
print(f"Popped: {last}, Remaining: {scores}")
second = scores.pop(1)
print(f"Popped index 1: {second}, Remaining: {scores}")
# clear: remove everything
backup = scores.copy()
scores.clear()
print(f"After clear: {scores}")
# del: remove by index or slice (a statement, not a method)
del backup[0]
print(f"After del [0]: {backup}")
▶ Output
After remove(72): [88, 95, 72, 61, 84] Popped: 84, Remaining: [88, 95, 72, 61] Popped index 1: 95, Remaining: [88, 72, 61] After clear: [] After del [0]: [72, 61]
What happened here: the big thing to notice is the difference between value and position. remove(72) deletes the first 72 it meets and leaves the second one alone. pop() is the only remover that also hands the item back, so you can catch it in a variable, which is handy when you want to grab and delete in one move. clear() empties the whole list, and del is a statement, not a method, that deletes by index. Notice we copied scores into backup before clearing, because once a list is cleared the data is gone.
Finding and Counting
📄 finding.py: index and count
grades = ["A", "B", "A", "C", "B", "A"]
# index: find the first position of a value
pos = grades.index("B")
print(f"First 'B' at index: {pos}")
# index with a start point
pos2 = grades.index("A", 1) # search from index 1 onward
print(f"Second 'A' at index: {pos2}")
# count: how many times a value appears
a_count = grades.count("A")
print(f"'A' appears {a_count} times")
# Safe search: check before indexing
if "D" in grades:
print(grades.index("D"))
else:
print("'D' not found, so no ValueError!")
▶ Output
First 'B' at index: 1 Second 'A' at index: 2 'A' appears 3 times 'D' not found, so no ValueError!
What happened here: index() and count() are the two read-only methods, so they look but never touch the list. There is one sharp edge though. If you call index() on a value that is not there, Python raises a ValueError and stops your program. That is why the last block checks if "D" in grades first. Same idea as checking your pocket for keys before you walk to a locked car: look first, then act.
Ordering
📄 ordering.py: sort and reverse
scores = [72, 88, 95, 61, 84]
# sort: reorders the list IN PLACE and returns None
scores.sort()
print(f"Sorted: {scores}")
scores.sort(reverse=True)
print(f"Descending: {scores}")
# reverse: flips the current order in place
names = ["Aviraj", "Rahul", "Niranjan"]
names.reverse()
print(f"Reversed: {names}")
# sort by a custom key (here, the second item of each tuple)
students = [("Anvi", 88), ("Aditi", 95), ("Anvay", 72)]
students.sort(key=lambda s: s[1])
print(f"By score: {students}")
students.sort(key=lambda s: s[1], reverse=True)
print(f"Top first: {students}")
▶ Output
Sorted: [61, 72, 84, 88, 95]
Descending: [95, 88, 84, 72, 61]
Reversed: ['Niranjan', 'Rahul', 'Aviraj']
By score: [('Anvay', 72), ('Anvi', 88), ('Aditi', 95)]
Top first: [('Aditi', 95), ('Anvi', 88), ('Anvay', 72)]
What happened here: sort() rearranges the list itself rather than handing you a new one. Add reverse=True to go high to low. The key argument is the real power tool. It tells sort() what to sort by, so key=lambda s: s[1] sorts each (name, score) tuple by its score instead of by name. Think of reverse() as flipping a row of books end to end, and sort() as lining them up by height.
Copying
📄 copying.py: copy() creates a shallow copy
original = [1, 2, 3, 4, 5]
duplicate = original.copy() # same as original[:]
duplicate.append(6)
print(f"Original: {original}") # unchanged
print(f"Copy: {duplicate}")
# Equivalent methods:
# copy1 = original[:]
# copy2 = list(original)
# All three create shallow copies
▶ Output
Original: [1, 2, 3, 4, 5] Copy: [1, 2, 3, 4, 5, 6]
What happened here: copy() gives you a fresh list, so appending 6 to the duplicate left the original untouched. Without it, writing duplicate = original would just add a second label to the same list, and a change through one name would show up under the other. The word “shallow” carries a warning, though. The copy is a new outer list, but if your list holds other lists inside it, both the original and the copy still point at those same inner lists. It is like photocopying a page that has a sticky note attached: you get a new page, but it still shares the one sticky note. For nested data you want deepcopy() from the built-in copy module instead.
Head-to-Head Comparisons
| Confusion | Method A | Method B | Key Difference |
|---|---|---|---|
| append vs extend | append(x) | extend(iter) | append adds one item; extend adds all items from iterable |
| remove vs pop | remove(value) | pop(index) | remove by value; pop by index (and returns it) |
| sort() vs sorted() | list.sort() | sorted(list) | sort() mutates in place (returns None); sorted() returns new list |
| reverse() vs reversed() | list.reverse() | reversed(list) | reverse() mutates; reversed() returns iterator |
| del vs remove vs pop | del list[i] | remove/pop | del by index (no return); remove by value; pop by index (returns) |
The Python Way
📄 pythonic.py: sort() returns None, not the sorted list
# Common trap: assigning the result of sort()
scores = [88, 72, 95]
result = scores.sort() # returns None!
print(f"result: {result}") # None
print(f"scores: {scores}") # sorted, but result is useless
# The Pythonic way: use sorted() to get a new list back
scores = [88, 72, 95]
result = sorted(scores)
print(f"result: {result}") # [72, 88, 95], a new sorted list
print(f"scores: {scores}") # [88, 72, 95], the original is unchanged
▶ Output
result: None scores: [72, 88, 95] result: [72, 88, 95] scores: [88, 72, 95]
The Python way: here is a rule that clears up half the confusion with lists. If a method changes the list, it returns None on purpose. Python does this so you cannot accidentally write scores = scores.sort() and lose your data, because scores would become None. When you want a new sorted list and want to keep the original, use the built-in sorted() instead. Same story with reverse() (which mutates) versus reversed() (which gives you something new). Mutating method, no return value. Built-in function, fresh result.
Common Mistakes
Mistake 1: Chaining mutating methods
🚫 Returns None
result = [3, 1, 2].sort() # None, because sort() returns None # result is None, not [1, 2, 3]!
✅ Use sorted() for chaining
result = sorted([3, 1, 2]) # returns [1, 2, 3]
Mistake 2: remove() on a value that doesn’t exist
🚫 Crash
items = [1, 2, 3] items.remove(99) # ValueError: list.remove(x): x not in list
✅ Check first
if 99 in items:
items.remove(99)
Conclusion
Eleven methods, grouped by purpose: add (append, extend, insert), remove (remove, pop, clear), find (index, count), order (sort, reverse), and copy (copy). Here is the detail worth remembering: 7 of the 11 methods (append, extend, insert, remove, clear, sort, reverse) change the list in place and return None. The other 4 hand you back something useful: pop() returns the item it removed, index() and count() return numbers, and copy() returns a new list. The two that never touch your list at all are index() and count().
Next up: List Comprehensions, the one-liners that replace whole loops when you build or transform a list. And if you want to see everything this series covers, from the basics all the way to the AI/ML chapters, head over to the Python + AI/ML tutorial series home.
Practice Exercises
- Exercise 1: Build a 10-item list using
append(),insert(),extend(). - Exercise 2: Remove duplicates while preserving order (no sets).
- Exercise 3: Implement sorted insert. Compare with sort-after-insert vs
bisect.
Frequently Asked Questions
What is the difference between append and extend in Python?
append(x) adds x as a single element to the end of the list. extend(iterable) adds each element from the iterable individually. [1, 2].append([3, 4]) gives [1, 2, [3, 4]]. [1, 2].extend([3, 4]) gives [1, 2, 3, 4].
What is the difference between sort() and sorted() in Python?
list.sort() sorts the list in place and returns None. sorted(list) returns a new sorted list and leaves the original unchanged. Use sort() when you don’t need the original order; use sorted() when you do.
What is the difference between remove() and pop() in Python?
remove(value) finds and removes the first occurrence of a value, and raises ValueError if it is not found. pop(index) removes by position and returns the removed element, and raises IndexError if the index is invalid. pop() with no argument removes the last element.
Why does sort() return None in Python?
Python’s design philosophy: methods that mutate objects in place return None to prevent confusion between the original and a modified copy. This is intentional, since it stops you from accidentally using the return value. Use sorted() when you need a return value.
How do I remove all occurrences of a value from a list?
Use a list comprehension: result = [x for x in my_list if x != value]. The remove() method only deletes the first occurrence. Using remove() in a while loop works but is slower: while value in my_list: my_list.remove(value).
What is the difference between del, remove, and pop?
del list[i] removes by index without returning the value. remove(value) removes by value (first occurrence). pop(index) removes by index and returns the removed element. Use del for index-based removal when you don’t need the value; pop() when you do.
Interview Questions on Python List Methods
Try each one aloud first. The phrasing you produce under mild pressure is what interviews measure.
Q: You wrote names = names.sort() and the next line crashes with AttributeError: 'NoneType' object has no attribute 'append'. What went wrong?
sort() sorts the list in place and returns None, so the assignment replaced the sorted list with None, and calling .append() on None crashed. The fix is either names.sort() on its own line, or names = sorted(names) if you really want an assignment. This is the single most common list bug interviewers probe for.
Q: Your teammate loops over a list and calls remove() inside the loop, and some items mysteriously get skipped. Why does that happen and how do you fix it?
Removing an item shifts everything after it one position left, but the loop counter still moves forward, so the element right after the removed one never gets visited. The clean fix is to build a new list with a comprehension, like items = [x for x in items if keep(x)], or to iterate over a copy with for x in items.copy(): while removing from the original. Never mutate a list you are actively looping over.
Q: A list holds other lists inside it. You call copy() on it, edit an inner list in the copy, and the original changes too. Explain.
copy() makes a shallow copy: a new outer list whose slots still point to the same inner objects. So mutating an inner list through the copy is visible through the original as well. When the nesting matters, use copy.deepcopy() from the copy module, which recursively duplicates the inner objects too.
Q: What is the difference between list1 + list2 and list1.extend(list2)?
The + operator builds and returns a brand new list, leaving both originals untouched. extend() mutates list1 in place and returns None, which is faster and more memory friendly when the list is large because nothing is copied. The += operator on a list behaves like extend(), not like +, which surprises many candidates.
Q: Why is insert(0, x) in a loop considered a performance problem, and what would you use instead?
Inserting at the front forces Python to shift every existing element one slot to the right, so each call costs O(n), and doing it in a loop becomes O(n squared). append(), by contrast, is amortized O(1) because it adds at the end. If you genuinely need fast inserts at both ends, use collections.deque, whose appendleft() is O(1).
Q: pop() and pop(0) both remove one element. Is there any practical difference?
Yes, a big one. pop() removes from the end, which is O(1), while pop(0) removes from the front, which is O(n) because every remaining element shifts left. That is why a list works well as a stack (append and pop at the end) but poorly as a queue; for queue behavior, reach for collections.deque and its popleft().
Further reading: for the full reference, see the official Python documentation.
Related Posts
Previous: Python: Lists, Creation, Indexing, Slicing Complete Guide
Next: Python List Comprehension: One-Liners That Replace Loops
Series Home: Python + AI/ML Tutorial Series

No comment