Python Tuples
A tuple is an ordered, immutable collection of items in Python, written with parentheses like (1, 2, 3). Tuples look similar to lists, but once created their contents cannot be changed — no appending, removing, or reassigning items. That immutability makes tuples faster, more memory-efficient, and safe to use as dictionary keys or set members, which is why Python uses them internally for things like function return values, multiple assignment, and fixed-shape records such as coordinates or database rows.
Overview / How it works
A tuple is a fixed-size, ordered sequence of object references. “Ordered” means the items keep the position you put them in, and you can access them by index. “Immutable” means that after the tuple is built, you cannot change which objects it points to — you cannot add, remove, or replace elements. This is the single biggest difference between a tuple and a list, which is mutable.
Internally, CPython represents a tuple as a PyTupleObject: a contiguous, fixed-length C array of pointers to the objects it contains. Because the array’s length is fixed at creation time, the interpreter allocates exactly the memory it needs in one shot — there is no extra “growth” capacity reserved the way there is for a list (which over-allocates so append() is cheap). This makes tuples slightly smaller in memory and slightly faster to create than an equivalent list. CPython also caches small tuples (length 0 and 1) and reuses the single empty tuple () as a singleton, so () is () is always True.
Immutability has an important consequence: a tuple is hashable as long as every item it contains is also hashable. That means a tuple of numbers or strings can be used as a dictionary key or placed inside a set, while a list never can. However, immutability only applies to the tuple’s own structure (which objects it references), not to the objects themselves. If a tuple holds a mutable object like a list, that inner list can still be modified in place — the tuple’s slots still point at the same list object, so the tuple itself hasn’t “changed,” but its contents can. This nuance trips up a lot of learners and is covered in the mistakes section below.
Tuples support every read-only sequence operation: indexing, slicing, iteration, membership testing with in, concatenation with +, repetition with *, and the built-ins len(), min(), max(), and sum(). They deliberately do not support any mutating method — there is no append, remove, sort, or item assignment. In fact, a plain tuple has only two methods: count() and index().
Syntax
t1 = (1, 2, 3) # parentheses (most common)
t2 = 1, 2, 3 # parentheses are optional here
t3 = (5,) # single-element tuple needs a trailing comma
t4 = () # empty tuple
t5 = tuple([1, 2, 3]) # built from an existing iterable
t6 = tuple("abc") # built from a string -> ('a', 'b', 'c')
| Form | Meaning |
|---|---|
() |
An empty tuple. |
(x,) |
A one-item tuple. The comma is required — (x) is just x in parentheses. |
(x, y, z) |
A multi-item tuple; parentheses are optional in assignment and return statements. |
tuple(iterable) |
Converts any iterable (list, string, range, generator, etc.) into a tuple. |
Because parentheses are also used for grouping expressions and function calls, the comma — not the parentheses — is what actually makes something a tuple. (1) is the integer 1; (1,) is a tuple containing 1.
Examples
Example 1: Creating and accessing tuples
point = (3, 4)
colors = "red", "green", "blue" # parentheses optional
empty = ()
single = (5,) # trailing comma required for a one-element tuple
print(point)
print(colors)
print(type(single))
print(colors[1])
print(colors[-1])
print(colors[0:2])
print(len(colors))
Output:
(3, 4)
('red', 'green', 'blue')
<class 'tuple'>
green
blue
('red', 'green')
3
Notice that colors was created without parentheses — the commas alone made it a tuple, and Python still displays it with parentheses when printed. Indexing and slicing work exactly as they do on lists and strings: negative indices count from the end, and slicing returns a new tuple.
Example 2: Unpacking and returning multiple values
def min_max(numbers):
return min(numbers), max(numbers)
scores = [88, 95, 72, 61, 100]
lowest, highest = min_max(scores)
print(f"Lowest: {lowest}, Highest: {highest}")
# Extended unpacking
first, *middle, last = scores
print(first, middle, last)
# Nested tuples
person = ("Ava", (1998, 4, 12), ["Python", "Rust"])
name, (birth_year, birth_month, birth_day), languages = person
print(f"{name} was born in {birth_year}")
languages.append("Go")
print(person)
Output:
Lowest: 61, Highest: 100
88 [95, 72, 61] 100
Ava was born in 1998
('Ava', (1998, 4, 12), ['Python', 'Rust', 'Go'])
The function min_max returns two values separated by a comma, which Python automatically packs into a tuple; the caller then unpacks that tuple into lowest and highest in one line. The starred expression *middle soaks up every element that isn’t matched by first or last. The final block shows nested unpacking (a tuple inside a tuple) and proves that a tuple’s immutability doesn’t extend to the mutable objects it holds — the list inside person was appended to in place, so person itself now displays a longer inner list even though the outer tuple was never reassigned.
Example 3: namedtuple and tuple methods
from collections import namedtuple
Point = namedtuple("Point", ["x", "y"])
p1 = Point(3, 4)
p2 = Point(x=0, y=0)
print(p1)
print(p1.x, p1.y)
print(p1 == (3, 4))
inventory = ("apple", "banana", "apple", "cherry", "apple")
print(inventory.count("apple"))
print(inventory.index("cherry"))
try:
inventory[0] = "kiwi"
except TypeError as e:
print(f"Error: {e}")
Output:
Point(x=3, y=4)
3 4
True
3
3
Error: 'tuple' object does not support item assignment
collections.namedtuple creates a lightweight, tuple-based class whose fields can be accessed by name (p1.x) as well as by position (p1[0]), which makes code far more readable than plain-index tuples while still being a real tuple underneath — it even compares equal to an ordinary tuple with the same values. The count() and index() calls show the only two built-in methods tuples have. The final block demonstrates immutability directly: attempting inventory[0] = "kiwi" raises a TypeError because tuples have no item-assignment support at the C level.
Under the hood
When Python executes a literal like t = 1, 2, 3, the compiler emits a BUILD_TUPLE bytecode instruction that pops the three values off the stack and packs them into one new PyTupleObject in a single allocation — this is “tuple packing.” When you later write a, b, c = t, the compiler emits UNPACK_SEQUENCE, which checks that the right-hand side has exactly three items and raises a ValueError immediately if the counts don’t match (this is why unpacking is safer than manual indexing — a shape mismatch fails loudly instead of silently reading the wrong value).
Because a tuple’s internal array is never resized after creation, the type simply does not implement the C-level slot (sq_ass_item) that would allow item assignment; that is the literal reason inventory[0] = "kiwi" raises TypeError rather than, say, silently failing. Hashing works by combining the hashes of every element into a single value (similar in spirit to hashing a string), which is only possible because the elements — and their positions — can never change after the tuple is built; if any element were mutable and mutated, the tuple’s hash would become inconsistent, which is exactly why Python refuses to hash a tuple containing a list (hash((1, [2, 3])) raises TypeError: unhashable type: 'list').
Common Mistakes
Mistake 1: Forgetting the trailing comma
t = (5)
print(type(t)) # not a tuple!
t_correct = (5,)
print(type(t_correct))
Output:
<class 'int'>
<class 'tuple'>
Parentheses around a single value are just grouping, like in a math expression — they don’t create a tuple. Only the trailing comma does. This is one of the most common tuple bugs, especially when a function is supposed to return a one-element tuple.
Mistake 2: Calling tuple() with multiple arguments
try:
numbers = tuple(1, 2, 3)
except TypeError as e:
print(f"Error: {e}")
numbers = tuple([1, 2, 3])
print(numbers)
Output:
Error: tuple expected at most 1 argument, got 3
(1, 2, 3)
The tuple() constructor takes a single iterable, not a list of separate values — that’s what the parentheses-with-commas syntax is for. Wrap the values in a list, another tuple, or any iterable before passing them in.
Mistake 3: Assuming a tuple is “fully frozen”
As shown in Example 2, a tuple that contains a mutable object (like a list or dict) only guarantees that its own slots never change — the mutable object inside can still be modified. If you need a truly deep-frozen structure, use immutable elements throughout (strings, numbers, other tuples) rather than assuming a tuple wrapper is enough.
Best Practices
- Use tuples for fixed, heterogeneous records (like a coordinate pair or a database row) and lists for homogeneous collections that may grow or shrink.
- Prefer
collections.namedtuple(or atyping.NamedTuple/dataclass(frozen=True)) over plain-index tuples once a record has more than two or three fields —order.totalis far clearer thanorder[3]. - Use tuple unpacking instead of manual indexing when consuming multiple return values:
lowest, highest = min_max(scores)is more readable thanresult = min_max(scores); lowest = result[0]. - Reach for tuples as dictionary keys when you need a composite key, e.g.
distances[(city_a, city_b)] = 120— this only works because tuples are hashable. - Always include the trailing comma for one-element tuples, and consider wrapping them in parentheses even where not strictly required, for readability:
(value,)rather thanvalue,. - Don’t fight the immutability — if you find yourself wanting to mutate a tuple repeatedly, that’s usually a sign you actually want a list.
Practice Exercises
- Write a function
divide(a, b)that returns both the quotient and the remainder as a tuple, then unpack the result into two variables and print them. - Given the tuple
weekdays = ("Mon", "Tue", "Wed", "Thu", "Fri"), use slicing to print just the first three days, and useinto check whether"Sat"is present. - Define a
namedtuplecalledBookwith fieldstitle,author, andyear. Create twoBookinstances, put them in a list, and print each one’s title and year using attribute access.
Summary
- A tuple is an ordered, immutable sequence written with parentheses and commas; the comma, not the parentheses, is what makes it a tuple.
- Tuples support indexing, slicing, iteration,
+,*, and only two methods:count()andindex()— no mutating operations exist. - Because they’re immutable, tuples of hashable elements are themselves hashable and can be used as dictionary keys or set members, unlike lists.
- Tuple packing/unpacking (
a, b = 1, 2) is how Python implements multiple assignment and multiple return values, backed by theBUILD_TUPLE/UNPACK_SEQUENCEbytecodes. - A tuple’s immutability only protects its own slots — a mutable object stored inside a tuple can still be changed in place.
collections.namedtuplegives tuples named, readable fields while keeping all of the performance and immutability benefits.
