List Methods: Adding, Removing and Sorting
Most list methods change the list in place and return None. Knowing which ones return a value and which do not prevents the most common list bug.
- Basics
- Data Types
- Operators
- Strings
- Control Flow
- Lists
- Tuples
- Sets
- Dictionaries
- Comprehensions
- Functions
- Advanced Functions
- Recursion
- Exception Handling
- File Handling
- Modules
- Standard Library
- OOP
- Advanced OOP
- Iterators and Generators
- Decorators
- Context Managers
- Descriptors and Dataclasses
- Python Internals
- Concurrency
- Regular Expressions
- Serialization
- Command Line Python
- Testing and Debugging
- Type Hints
- Performance
- Python Security
- DSA with Python
The rule that prevents most list bugs
items = [3, 1, 2]
result = items.sort()
print(result) # None <- the method returned nothing
print(items) # [1, 2, 3] <- but the list was sorted
items = [3, 1, 2]
result = sorted(items)
print(result) # [1, 2, 3] <- a new list
print(items) # [3, 1, 2] <- unchangedAlmost every list method modifies the list in place and returns None. Writing items = items.sort() throws the list away and leaves you holding None. This is the single most common list mistake.
In place, returns None | Returns a new object |
|---|---|
append, extend, insert | sorted(items) |
remove, clear, reverse, sort | reversed(items) |
| - | items.copy(), items[:] |
pop is the exception: it changes the list and returns the removed item. | |
Adding items
items = [1, 2]
items.append(3) # add ONE item at the end
print(items) # [1, 2, 3]
items.append([4, 5]) # appends the LIST as a single item
print(items) # [1, 2, 3, [4, 5]]
items = [1, 2]
items.extend([3, 4]) # add each item of an iterable
print(items) # [1, 2, 3, 4]
items.extend("ab") # any iterable, including a string
print(items) # [1, 2, 3, 4, 'a', 'b']
items = [1, 2, 3]
items.insert(1, "new") # insert BEFORE index 1
print(items) # [1, 'new', 2, 3]
items.insert(0, "first") # at the front
items.insert(999, "last") # an index past the end just appends
print(items) # ['first', 1, 'new', 2, 3, 'last']append vs extend
a = [1, 2]
a.append([3, 4])
print(a, len(a)) # [1, 2, [3, 4]] 3
b = [1, 2]
b.extend([3, 4])
print(b, len(b)) # [1, 2, 3, 4] 4
c = [1, 2]
c += [3, 4] # same as extend
print(c) # [1, 2, 3, 4]Removing items
items = ["a", "b", "c", "b"]
items.remove("b") # removes the FIRST match only
print(items) # ['a', 'c', 'b']
# items.remove("z") # ValueError if not present
last = items.pop() # removes and returns the last item
print(last, items) # b ['a', 'c']
first = items.pop(0) # removes and returns by index
print(first, items) # a ['c']
items = ["a", "b", "c"]
del items[1] # delete by index
print(items) # ['a', 'c']
del items[:] # delete everything, same as clear()
print(items) # []
items = ["a", "b", "c"]
items.clear()
print(items) # []Removing safely
items = ["a", "b", "c"]
if "z" in items:
items.remove("z")
# or
try:
items.remove("z")
except ValueError:
pass
# Removing every match, not just the first
items = [1, 2, 3, 2, 4, 2]
items = [n for n in items if n != 2]
print(items) # [1, 3, 4]Sorting
numbers = [5, 2, 9, 1]
numbers.sort() # in place, ascending
print(numbers) # [1, 2, 5, 9]
numbers.sort(reverse=True)
print(numbers) # [9, 5, 2, 1]
print(sorted([5, 2, 9, 1])) # a new sorted list, original untouchedSorting with a key
words = ["banana", "kiwi", "apple", "fig"]
print(sorted(words, key=len)) # ['fig', 'kiwi', 'apple', 'banana']
print(sorted(words, key=str.lower)) # case insensitive
print(sorted(words, key=lambda w: w[-1])) # by last letter
people = [
{"name": "Meera", "age": 27},
{"name": "Arun", "age": 31},
{"name": "Sara", "age": 24},
]
print(sorted(people, key=lambda p: p["age"]))
print(sorted(people, key=lambda p: p["name"]))
# Sort by two things: age descending, then name ascending
print(sorted(people, key=lambda p: (-p["age"], p["name"])))The key function is called once per item, and the results are what get compared. It never changes the items themselves.
Sorting is stable
records = [("b", 2), ("a", 1), ("c", 2), ("d", 1)]
by_number = sorted(records, key=lambda r: r[1])
print(by_number) # [('a', 1), ('d', 1), ('b', 2), ('c', 2)]Items comparing equal keep their original relative order. That guarantee lets you sort by a secondary key first and a primary key second, which is often simpler than building a tuple key.
Reversing
items = [1, 2, 3]
items.reverse() # in place
print(items) # [3, 2, 1]
print(list(reversed(items))) # [1, 2, 3] - a new list
print(items[::-1]) # [1, 2, 3] - a new list via slicingCopying
original = [1, 2, 3]
alias = original # NOT a copy
shallow = original.copy() # a copy
also_shallow = original[:] # a copy
also_shallow_2 = list(original) # a copy
alias.append(4)
print(original) # [1, 2, 3, 4] - changed through the alias
print(shallow) # [1, 2, 3] - unaffectedAll three copy forms are shallow: the new list is independent, but the objects inside are shared. For nested lists that matters, and copy.deepcopy is the answer. The internals notes cover this fully.
The full method list
| Method | Does | Returns |
|---|---|---|
append(x) | Add one item at the end | None |
extend(iterable) | Add every item of an iterable | None |
insert(i, x) | Insert before index i | None |
remove(x) | Remove the first match | None |
pop([i]) | Remove and return by index, last by default | the item |
clear() | Remove everything | None |
index(x[, start, end]) | Position of the first match | int |
count(x) | How many matches | int |
sort(key=, reverse=) | Sort in place | None |
reverse() | Reverse in place | None |
copy() | Shallow copy | a new list |
Cost of each operation
| Operation | Cost | Note |
|---|---|---|
items[i] | Constant | Direct access |
append, pop() | Constant | At the end, cheap |
insert(0, x), pop(0) | Proportional to length | Everything shifts |
x in items | Proportional to length | Scans |
sort | n log n | Very fast in practice |
If you repeatedly add and remove at the front, a list is the wrong structure. Use collections.deque, covered in the standard library notes.
Common mistakes
- Writing
items = items.sort()and losing the list. - Using
appendwhenextendwas meant, producing a nested list. - Calling
removeon a value that may be absent. - Expecting
removeto delete every match. - Assuming
copy()is deep. - Using
pop(0)in a loop over a large list.
Best practices
- Use
sorted()when you need both the original and the sorted version. - Use a comprehension to filter, rather than removing inside a loop.
- Use
key=with a tuple for multi level sorting. - Reach for
dequewhen the front of the collection is busy.
Practice
- Explain why
numbers = numbers.sort()leavesnumbersasNone. - Sort a list of records by grade descending and then by name ascending.
- Remove every occurrence of a value from a list in three different ways.
- Demonstrate that
copy()is shallow using a list of lists. - Compare the time taken by
pop(0)andpop()on a list of 100 000 items.
Conclusion
List methods change the list and return None; the functions sorted and reversed return something new. Keep that split straight, use key= for anything beyond a plain sort, and remember that the front of a list is expensive.