Tuples and when immutability helps
A tuple is a list you cannot change. That sounds like a list with a feature removed, and the first reaction is usually "why would I want that?" The answer is worth ten minutes.
Making one
Round brackets, or no brackets at all:
point = (10, 20)
rgb = (255, 128, 0)
person = "Priya", 28, "Pune" # brackets optional
empty = ()
Indexing, slicing, len, in and looping all work exactly as they do on lists:
print(point[0]) # 10
print(point[-1]) # 20
print(len(rgb)) # 3
print(255 in rgb) # True
What does not work is changing it:
point[0] = 99
TypeError: 'tuple' object does not support item assignment
No append, no remove, no sort. Once made, it is fixed.
The one-item trap
not_a_tuple = (5)
print(type(not_a_tuple)) # <class 'int'>
actual_tuple = (5,)
print(type(actual_tuple)) # <class 'tuple'>
Brackets alone mean grouping, as in arithmetic. The comma makes the tuple, not the brackets. A single-item tuple needs that trailing comma, and forgetting it produces a confusing error some lines later.
Why bother
It says the shape will not change
point = (10, 20)
A reader knows immediately that this is a pair, will stay a pair, and that no code anywhere appends a third value. A list makes no such promise, and checking would mean reading everything that touches it.
This is the main reason to use tuples, and it is about communication rather than technology.
It can be a dictionary key
From the last lesson: keys must be immutable. Lists cannot be keys; tuples can.
locations = {
(19.07, 72.87): "Mumbai",
(18.52, 73.86): "Pune",
}
print(locations[(18.52, 73.86)])
Pune
That is genuinely useful for grids, coordinates and any compound key. A
spreadsheet cell keyed by (row, column) is a tuple key.
Nothing can quietly modify it
DEFAULT_SIZES = ("small", "medium", "large")
If that were a list, any code anywhere could append to it, and since the variables lesson you know a list passed around is one list with several labels. A tuple removes the possibility.
It is slightly faster and smaller
True, and almost never the reason. Choose a tuple because the data should not change, not to save microseconds.
Unpacking
Tuples make multiple return values and multiple assignment read naturally:
point = (10, 20)
x, y = point
print(x, y) # 10 20
The swap from the variables lesson is a tuple:
a, b = b, a
The right-hand side builds a tuple, then unpacks it into the names on the left.
enumerate and .items() both hand you tuples:
for index, fruit in enumerate(fruits):
...
for key, value in prices.items():
...
You have been unpacking tuples since module 3 without being told.
Use _ for parts you do not need:
name, _, city = ("Priya", 28, "Pune")
And * for the rest:
first, *others = (1, 2, 3, 4)
print(others) # [2, 3, 4] — note: a list
Tuples in practice
Functions returning more than one value return tuples. You have already seen one:
print(divmod(17, 5)) # (3, 2)
quotient, remainder = divmod(17, 5)
That pattern is everywhere once you start writing functions in module 5.
Records are a common use:
people = [
("Priya", 28, "Pune"),
("Arjun", 34, "Delhi"),
]
for name, age, city in people:
print(f"{name} ({age}) — {city}")
Each record is fixed in shape: always three fields, always in that order. A tuple expresses that; a list would not.
Worth flagging honestly: past three or four fields, person[2] becomes as
unreadable as it was for lists. At that point you want a dictionary, or a
dataclass from the classes module. Tuples are best for small, fixed groupings.
Immutable does not mean frozen all the way down
data = (1, 2, [3, 4])
data[2].append(5)
print(data)
(1, 2, [3, 4, 5])
The tuple did not change — it still holds the same three items, and the third is still the same list. That list changed.
Immutability applies to the tuple's own contents, not to whatever those contents
point at. Same one-level-deep idea as .copy() on a list. A tuple containing
only immutable things is immutable all the way down; one containing a list is
not.
Converting
numbers = [1, 2, 3]
as_tuple = tuple(numbers)
back = list(as_tuple)
The usual reason is needing a dictionary key from a list:
key = tuple(sorted(["b", "a"]))
counts[key] = counts.get(key, 0) + 1
sorted() first, so ["a", "b"] and ["b", "a"] produce the same key.
When to use which
Reach for a tuple when the collection is a fixed grouping of related values whose shape will not change — a coordinate, an RGB colour, a record, a set of constants, a dictionary key.
Reach for a list when items will be added, removed or reordered, or when you have an unknown number of the same kind of thing.
A useful test: if you would naturally give each position a name, it is probably a tuple or a dictionary. If they are all interchangeable items of the same kind, it is a list.
Practice
- Make a tuple of three cities. Print the first and last, loop over it, then try to change one and read the error.
- Run
type((5))andtype((5,)). Explain the difference in one sentence. - Build a dictionary keyed by
(row, column)tuples representing a 3 x 3 noughts-and-crosses board, and print one cell. - Unpack
("Priya", 28, "Pune", "Engineer")into name and city, discarding the rest. - Store five people as tuples in a list. Loop and print each formatted. Then
sort by age using
sorted(..., key=...)— you will need to index the tuple inside the key. - Create
data = (1, 2, [3, 4]), append to the inner list, and explain why the tuple is still considered immutable. - Write code that counts how often each unordered pair of letters appears in a
list of two-letter strings, so
"ab"and"ba"count together. You will need a sorted tuple as the key.
Next: sets, for when you care what is in a collection but not how many times or in what order.
Stuck on this lesson?
Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.
About the internship