Python Lists: The Complete Guide with All Methods
Master Python lists — creation, indexing, slicing, comprehensions, and every built-in method (append, extend, insert, sort, and more) with runnable examples and gotchas.
Lists are the workhorse of Python. They’re mutable, ordered, and can hold anything — numbers, strings, other lists, even functions. If you’re coming from another language, think of them as resizable arrays with a huge standard library of built-in behavior. This guide covers everything from the fundamentals to every list method you’ll actually use.
List Fundamentals
Creating Lists
1
2
3
4
5
6
empty = []
numbers = [1, 2, 3, 4, 5]
mixed = [1, "two", 3.0, [4, 5]]
from_range = list(range(5)) # [0, 1, 2, 3, 4]
from_string = list("abc") # ['a', 'b', 'c']
squares = [x**2 for x in range(6)] # comprehension: [0, 1, 4, 9, 16, 25]
List comprehensions are the idiomatic way to build a list from an iterable in one line. They’re faster than an equivalent for loop with repeated .append() calls because the loop runs in C rather than bytecode-by-bytecode in the interpreter.
Indexing and Negative Indexing
1
2
3
4
fruits = ["apple", "banana", "cherry", "date"]
fruits[0] # "apple" — first element
fruits[-1] # "date" — last element
fruits[-2] # "cherry" — second to last
Negative indices count from the end, so -1 is always the last item without needing len(fruits) - 1.
Slicing
1
2
3
4
5
fruits[1:3] # ["banana", "cherry"] — index 1 up to (not including) 3
fruits[:2] # ["apple", "banana"] — from start
fruits[2:] # ["cherry", "date"] — to end
fruits[::-1] # ["date", "cherry", "banana", "apple"] — reversed copy
fruits[::2] # every second element
Slicing always returns a new list — it never mutates the original, and it never raises an IndexError even if the bounds are out of range.
Mutation
1
2
3
fruits[0] = "avocado" # replace by index
fruits[1:3] = ["kiwi", "mango"] # replace a slice with a different-length slice
del fruits[0] # remove by index
Lists are mutable, so passing one into a function lets that function change it in place — a common source of bugs if you don’t intend that.
Nesting
1
2
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
matrix[1][2] # 6 — row 1, column 2
Nested lists are just lists whose elements happen to be lists. There’s no special “2D array” type in core Python.
Iteration and Membership
1
2
3
4
5
6
7
for fruit in fruits:
print(fruit)
for i, fruit in enumerate(fruits):
print(i, fruit)
"kiwi" in fruits # True — membership test, O(n)
Copying: Shallow vs Deep
This is where most bugs come from:
1
2
3
4
5
6
7
8
9
10
import copy
original = [[1, 2], [3, 4]]
shallow = original.copy() # or list(original) or original[:]
shallow[0].append(99)
print(original) # [[1, 2, 99], [3, 4]] — inner list is SHARED!
deep = copy.deepcopy(original)
deep[0].append(100)
print(original) # unaffected
.copy(), list(x), and x[:] all produce a shallow copy: the outer list is new, but nested mutable objects (lists, dicts) inside it are still shared references. If your list contains only immutable elements (numbers, strings, tuples), a shallow copy is all you need. If it contains nested lists or dicts you plan to mutate, use copy.deepcopy().
List Methods
list.append()
1
2
3
nums = [1, 2, 3]
nums.append(4)
print(nums) # [1, 2, 3, 4]
Adds a single item to the end of the list, in place. Returns None — don’t write nums = nums.append(4), you’ll end up with None. Gotcha: append() adds the argument as one element, even if it’s a list — nums.append([5, 6]) gives [1, 2, 3, [5, 6]], not a flattened list. Use extend() for that.
list.extend()
1
2
3
nums = [1, 2, 3]
nums.extend([4, 5])
print(nums) # [1, 2, 3, 4, 5]
Appends every item from an iterable individually, in place. Equivalent to nums += [4, 5]. Use when you want to merge two lists (or add multiple items) rather than nest one inside the other.
list.insert()
1
2
3
nums = [1, 2, 4]
nums.insert(2, 3)
print(nums) # [1, 2, 3, 4]
Inserts an item at a given index, shifting everything after it to the right. Gotcha: inserting near the front of a large list is O(n) because every subsequent element has to move — if you’re doing this repeatedly, consider collections.deque instead.
list.remove()
1
2
3
nums = [1, 2, 3, 2]
nums.remove(2)
print(nums) # [1, 3, 2] — only the FIRST match is removed
Removes the first occurrence of a value by value, not by index. Raises ValueError if the value isn’t present — wrap in a try/except or check in first if that’s expected.
list.pop()
1
2
3
nums = [1, 2, 3]
last = nums.pop() # removes and returns 3
first = nums.pop(0) # removes and returns 1
Removes and returns the item at the given index (default: the last one). This is the only removal method that gives you the value back, which makes it perfect for stack (pop()) and queue-like (pop(0)) patterns — though for queues, deque.popleft() is O(1) versus pop(0)’s O(n).
list.clear()
1
2
3
nums = [1, 2, 3]
nums.clear()
print(nums) # []
Removes all items in place, leaving an empty list. Equivalent to del nums[:], but more readable. Use when you want to empty a list that other variables still reference (unlike nums = [], which just rebinds the name and leaves other references pointing at the old, non-empty list).
list.copy()
1
2
3
4
nums = [1, 2, 3]
backup = nums.copy()
backup.append(4)
print(nums, backup) # [1, 2, 3] [1, 2, 3, 4]
Returns a shallow copy — see the fundamentals section above for the nested-list caveat.
list.index()
1
2
3
fruits = ["apple", "banana", "cherry"]
fruits.index("cherry") # 2
fruits.index("banana", 1) # search starting at index 1
Returns the index of the first matching value. Raises ValueError if not found — check with in first, or catch the exception, rather than letting it crash unhandled.
list.count()
1
2
nums = [1, 2, 2, 3, 2]
nums.count(2) # 3
Returns how many times a value appears. Use when you need a quick frequency check on one value; for counting all values at once, collections.Counter is far more efficient than calling .count() in a loop.
list.sort()
1
2
3
4
5
6
nums = [3, 1, 4, 1, 5]
nums.sort() # [1, 1, 3, 4, 5], in place
nums.sort(reverse=True) # [5, 4, 3, 1, 1]
words = ["banana", "kiwi", "fig"]
words.sort(key=len) # ["fig", "kiwi", "banana"]
Sorts in place and returns None. Use the key parameter to sort by a derived value instead of the raw elements. Gotcha: if you need the original order preserved and a new list returned, use the builtin sorted(nums) instead — .sort() mutates and returns nothing.
list.reverse()
1
2
3
nums = [1, 2, 3]
nums.reverse()
print(nums) # [3, 2, 1]
Reverses the list in place. For a reversed copy without touching the original, use nums[::-1] or reversed(nums).
Method Summary Table
| Method | Returns | Mutates? |
|---|---|---|
append(x) | None | Yes |
extend(iter) | None | Yes |
insert(i, x) | None | Yes |
remove(x) | None | Yes |
pop(i=-1) | The removed item | Yes |
clear() | None | Yes |
copy() | New list (shallow) | No |
index(x) | Index (int) | No |
count(x) | Count (int) | No |
sort(key=, reverse=) | None | Yes |
reverse() | None | Yes |
Performance Notes
A few facts worth internalizing if you use lists heavily:
- Appending is amortized O(1): CPython over-allocates space when a list grows, so
append()doesn’t reallocate on every call — only occasionally, when the reserved space runs out. - Inserting or removing at the front is O(n): both
insert(0, x)andpop(0)have to shift every remaining element by one position. If you need fast operations at both ends, usecollections.dequeinstead of a list. - Membership tests (
in) are O(n): a list has to be scanned linearly. If you’re checking membership repeatedly against a large collection, convert it to asetfirst —setmembership is O(1) on average. - List comprehensions beat
for+append()loops: the comprehension’s iteration happens in a single specialized bytecode loop, avoiding the repeated attribute lookup (nums.append) and function-call overhead of calling a bound method on every iteration.
Lists vs. Tuples
A common early-Python question is when to reach for a list versus a tuple. The short answer: use a list when the collection needs to change size or contents over its lifetime (mutable), and a tuple when it represents a fixed, immutable record — coordinates, RGB values, a row read from a database. Tuples are also hashable (as long as their contents are), so they can be used as dictionary keys or set members; lists cannot, since Python won’t hash a mutable object.
1
2
point = (3, 4) # tuple: fixed pair, won't change
scores = [90, 85, 77] # list: will grow/shrink/reorder over time
Where to Go Next
Lists are one piece of Python’s core toolkit. To round it out:
- If you’re wrangling key-value data instead of ordered sequences, see the Python Dictionaries: The Complete Guide for the dict equivalent of everything covered here.
- Many list methods are paired with global functions like
sorted(),len(), andenumerate()— the full rundown is in the Python Built-in Functions Reference. - If you’re feeding lists into an AI pipeline (batching prompts, chunking documents), see how async iteration over sequences works in Python Async Await for AI Pipelines.
Once you’re comfortable with these eleven methods, slicing, and the shallow-vs-deep copy distinction, you know essentially everything you need to use Python lists effectively in real code.
