PY-01-008-P · Primer — Lists & Tuples
The idea in one sentence
A list is an ordered, changeable collection you write with square brackets [1, 2, 3], and a tuple is its ordered but unchangeable twin written with parentheses (1, 2, 3) — same look-up powers, one crucial difference: you can edit a list after making it, but a tuple is frozen for life.
Why it exists
Almost everything you'll ever program is really a collection of things in order: the lines in a file, the players in a game, the rows returned from a database, the words in a sentence. You need one container that holds them in sequence, lets you grab item number 3, and lets you loop over every one — that's the list, Python's everyday workhorse for "a bunch of stuff I'll add to, sort, and change." But sometimes a group of values belongs together permanently and must not wobble — the (latitude, longitude) of a fixed location, the (width, height) of a screen, the red-green-blue of a color. For those, Python gives you the tuple: it signals "these values are a fixed record, treat them as one unbroken thing," and because it can't change, Python can even use it as a dictionary key or a set member (a list can't do either). Having both means your code says what you mean — "this grows" versus "this is fixed" — instead of leaving the reader to guess.
How it works
Both are sequences, so they share the same access tools: indexing with list[0] (counting starts at zero, and list[-1] is the last item), and slicing with list[1:3] to pull out a sub-range. Lists add mutating methods — .append(x) adds to the end, .sort() reorders in place, and list[i] = new overwrites a slot — while tuples deliberately have none of those, because they can't change. Both let you iterate with for item in seq: and both support unpacking, where x, y = point hands each value to a name in one clean line. The example below builds a list, mutates it three ways, slices it, then unpacks a tuple:
colors = ["red", "green", "blue"]
colors.append("yellow") # add to the end (lists can grow)
colors[0] = "crimson" # overwrite slot 0 (lists can change)
print(colors) # ['crimson', 'green', 'blue', 'yellow']
print(colors[1:3]) # ['green', 'blue'] (a slice: items 1 and 2)
print(len(colors)) # 4 (how many items)
point = (3, 4) # a tuple: a fixed (x, y) record
x, y = point # unpacking: x <- 3, y <- 4
print(x, y) # 3 4
The tuple point was never modified — it was created whole and read whole, which is exactly the promise a tuple makes.
The one gotcha
"Immutable" means the tuple's slots can't be reassigned — not that everything inside is frozen. If a tuple holds a list, you can still change that inner list, because the tuple's slot still points to the very same list object; what you can't do is swap the slot for a different object. This trips up nearly everyone the first time they see it:
grid = ([1, 2], [3, 4]) # a tuple whose items are lists
grid[0].append(99) # OK: we mutate the list, not the tuple slot
print(grid) # ([1, 2, 99], [3, 4])
grid[0] = [5, 6] # ERROR: reassigning a tuple slot is forbidden
# TypeError: 'tuple' object does not support item assignment
So the rule is precise: a tuple guarantees its own structure won't change, but it makes no promise about the mutability of the objects it contains. One more beginner snag lives right next door — a one-item tuple needs a trailing comma: (5,) is a tuple, but (5) is just the number 5 in parentheses.
See it — choosing a list or a tuple, and what each allows
flowchart TD
A["I have a group<br/>of values in order"] --> B{"Will the collection<br/>change over time?"}
B -->|"Yes: add, remove,<br/>sort, overwrite"| C["Use a LIST<br/>colors = [ ... ]"]
B -->|"No: a fixed record<br/>that stays put"| D["Use a TUPLE<br/>point = ( ... )"]
C --> E[".append, .sort,<br/>list[i] = new all work"]
D --> F["No mutating methods;<br/>can be a dict key or<br/>set member"]
F --> G{"Does it hold a<br/>list inside?"}
G -->|"Yes"| H["Inner list can still<br/>be mutated"]
G -->|"No"| I["Fully unchangeable"]
The one-line takeaway
Reach for a list [...] when the collection will change, and a tuple (...) when it's a fixed record — both index, slice, iterate, and unpack the same way, but only the list can grow, shrink, or be rewritten.
Where you'll meet it
You'll meet lists within your first hour of Python and never stop: gathering results in a loop with .append(), sorting scores, reading a file into a list of lines, or slicing off the first ten items of a search result. Tuples show up the moment a function needs to hand back two things at once — return name, age quietly packs a tuple you then unpack with name, age = get_user() — and they're everywhere in coordinates, RGB colors, database rows, and the keys of a dictionary that must be built from several fields. Unpacking becomes second nature in for x, y in points: and in the classic swap a, b = b, a. Knowing which to pick — "this list grows" versus "this record is fixed" — is one of the clearest early signals that you're thinking like a Python programmer, and it pays off again the day you try to use a mutable list where an immutable tuple was required and Python politely refuses.
Named concepts: mutable lists vs immutable tuples, indexing, slicing, and methods, iteration and unpacking, when to choose each
