Lists
Python list basics: append, extend, slicing, shallow copies, and the shared-reference trap with [[]] * n.
A Python list is a mutable, ordered sequence of items. You create one with square brackets ([1, 2, 3]) or by calling list(iterable). Lists grow with .append() and .extend(), shrink with .pop() and .remove(), and can be sliced with xs[start:stop].
The most common list operations: .append(x) adds one item to the end, .extend(iterable) appends every item from another iterable, .insert(i, x) inserts at a specific index, .pop() removes and returns the last item, and .remove(x) removes the first occurrence of a value. Appending and popping at the right end (index -1) is O(1) - fast regardless of list size. Inserting or removing at the left end (index 0) is O(n) because every other item must shift.
xs = [1, 2, 3]
xs.append(4) # [1, 2, 3, 4]
xs.extend([5, 6]) # [1, 2, 3, 4, 5, 6]
xs.insert(0, 0) # [0, 1, 2, 3, 4, 5, 6] -- O(n), shifts everything
xs.pop() # returns 6, list is [0, 1, 2, 3, 4, 5]
xs.remove(0) # removes first 0, list is [1, 2, 3, 4, 5]
len(xs) # 5
3 in xs # True
xs.index(3) # 2 (position of the value 3)Slicing returns a new list containing the selected items. xs[a:b] gives items from index a up to (but not including) b. Negative indices count from the end: xs[-1] is the last item. xs[::-1] steps backwards and returns a reversed copy. xs[:] copies the entire list shallowly - useful when you need a separate list that starts with the same values.
xs = [10, 20, 30, 40, 50]
xs[1:3] # [20, 30] -- items at index 1 and 2
xs[:2] # [10, 20] -- first two
xs[2:] # [30, 40, 50] -- from index 2 to end
xs[-1] # 50 -- last item
xs[::-1] # [50, 40, 30, 20, 10] -- reversed copy
xs[:] # [10, 20, 30, 40, 50] -- shallow copy
copy = xs[:]
copy.append(99)
xs # [10, 20, 30, 40, 50] -- original unchanged[[]] * n looks like it creates n independent empty lists, but it actually creates n references to the same inner list. Appending to any one of them mutates all of them. The fix is a list comprehension: [[] for _ in range(n)] calls [] once per iteration, producing n distinct list objects.
# Bug: all three rows point to the same list
grid = [[]] * 3
grid[0].append(1)
grid # [[1], [1], [1]] -- all three changed!
# Fix: comprehension creates a new [] each iteration
grid = [[] for _ in range(3)]
grid[0].append(1)
grid # [[1], [], []] -- only row 0 changed
# Same trap with any mutable default
row = [0] * 5 # [0, 0, 0, 0, 0] -- safe: ints are immutable
matrix = [[0] * 5 for _ in range(3)] # correct 3x5 gridIn production
[[]] * n creates n references to the same inner list - appending to one mutates all of them. Use [[] for _ in range(n)] instead. Lists are O(1) for .append() and .pop() at the right end but O(n) for .pop(0) and .insert(0, x) because every item must shift; for FIFO queues reach for collections.deque, which is O(1) at both ends.
Enjoyed this? Get more essays on software craft delivered to your inbox.
Subscribe free