A tuple is Python's second sequence type — an ordered collection of values, just like a list, but with one defining twist: it's immutable. Once you create a tuple, you can't change, add, or remove its items.
✨Big idea: if a list is a notepad you can edit, a tuple is a printed receipt. Same items, same order — but frozen in place. Use tuples for fixed collections that should never change after creation.
Use parentheses with comma-separated values (and yes, items can mix types):
developer = ('Alice', 34, 'Rust Developer')
numbers = (1, 2, 3, 4, 5)
empty = ()Parentheses alone don't create a tuple — the commas do. A single value in parens is just a grouped expression:
single = (42) # ⚠️ This is an int, NOT a tuple!
real_single = (42,) # ✅ Tuple of one item — note the trailing comma
implicit = 1, 2, 3 # ✅ Also a tuple (parens are optional)| Trait | List | Tuple |
|---|---|---|
| Syntax | [1, 2, 3] |
(1, 2, 3) |
| Ordered? | ✅ Yes | ✅ Yes |
| Mutable? | ✅ Yes — edit in place | ❌ No — immutable |
| Zero-indexed? | ✅ Yes | ✅ Yes |
| Supports slicing? | ✅ Yes | ✅ Yes |
Supports in? |
✅ Yes | ✅ Yes |
| Supports unpacking? | ✅ Yes | ✅ Yes |
| Usable as a dict key? | ❌ No | ✅ Yes (if items are hashable) |
| Memory & speed | Slightly heavier | Slightly lighter & faster |
Try to update a tuple and Python raises TypeError:
programming_languages = ('Python', 'Java', 'C++', 'Rust')
programming_languages[0] = 'JavaScript'
# TypeError: 'tuple' object does not support item assignmentAnd del on an index also fails:
developer = ('Jane Doe', 23, 'Python Developer')
del developer[1]
# TypeError: 'tuple' object doesn't support item deletionflowchart TD
TRY["You write code that<br>changes a tuple"] --> KIND{"What kind of change?"}
KIND -- "tup[i] = x" --> ASSIGN["❌ TypeError<br>'tuple' object does not<br>support item assignment"]
KIND -- "del tup[i]" --> DEL["❌ TypeError<br>'tuple' object doesn't<br>support item deletion"]
KIND -- "tup.append(x)<br>tup.remove(x)" --> ATTR["❌ AttributeError<br>'tuple' object has no<br>attribute 'append'/'remove'"]
Mental model: an immutable object is frozen — you can read it freely, but you can't rewrite it. To "change" a tuple, you create a new one.
Bracket notation with the index number:
developer = ('Alice', 34, 'Rust Developer')
developer[0] # 'Alice'
developer[1] # 34
developer[2] # 'Rust Developer'numbers = (1, 2, 3, 4, 5)
numbers[-1] # 5 ← last
numbers[-2] # 4 ← second to lastnumbers = (1, 2, 3, 4, 5)
numbers[7]
# IndexError: tuple index out of rangeConvert any iterable into a tuple:
developer = 'Jessica'
tuple(developer) # ('J', 'e', 's', 's', 'i', 'c', 'a')
tuple([1, 2, 3]) # (1, 2, 3) ← list → tuple
tuple(range(4)) # (0, 1, 2, 3)
tuple() # () ← empty tupleWorks with strings, lists, ranges, sets, dictionaries (gives the keys), and any other iterable.
Identical to lists:
programming_languages = ('Python', 'Java', 'C++', 'Rust')
'Rust' in programming_languages # True
'JavaScript' in programming_languages # False
'JavaScript' not in programming_languages # Truedeveloper = ('Alice', 34, 'Rust Developer')
name, age, job = developer
print(name) # 'Alice'
print(age) # 34
print(job) # 'Rust Developer'developer = ('Alice', 34, 'Rust Developer')
name, *rest = developer
print(name) # 'Alice'
print(rest) # [34, 'Rust Developer'] ← always a LIST, even from a tupleSubtle but important: *rest always collects into a list, regardless of whether you unpacked a list, tuple, string, or any other iterable.
Slicing a tuple gives you a brand-new tuple:
desserts = ('cake', 'pie', 'cookies', 'ice cream')
desserts[1:3] # ('pie', 'cookies')The start index is inclusive, the stop index is exclusive — same as lists.
desserts[:2] # ('cake', 'pie')
desserts[2:] # ('cookies', 'ice cream')
desserts[::-1] # ('ice cream', 'cookies', 'pie', 'cake') ← reversed copySlicing never raises IndexError — out-of-range stops are silently clipped, just like with lists.
flowchart TD
Q["Need an ordered collection of values"] --> CHANGE{"Will the contents change<br>after creation?"}
CHANGE -- Yes --> LIST["✅ Use a LIST<br>append, remove, sort, etc."]
CHANGE -- No --> FIXED{"Is it a fixed record<br>(coords, RGB, db row)?"}
FIXED -- Yes --> TUP["✅ Use a TUPLE<br>safe, hashable, faster"]
FIXED -- No --> EITHER["Either works —<br>prefer tuple for safety"]
- Fixed records — (latitude, longitude), (R, G, B), (year, month, day).
- Multiple return values from a function:
return min_val, max_val, average. - Dictionary keys that combine fields:
prices[('USD', 'apple')] = 1.20. - Constants — a tuple of allowed colors, a tuple of HTTP method names, etc.
- Performance — tuples are slightly smaller and faster to construct than lists.
- Growth — collecting items one at a time.
- Mutation — sorting, removing, updating in place.
- Open-ended sequences where the length isn't known up front.
# Create
point = (3, 7) # a 2D coordinate (classic tuple use case)
rgb = (255, 128, 0) # an RGB color
langs = ('Python', 'Rust', 'Go')
# Read
print(point[0]) # 3
print(rgb[-1]) # 0
# Membership
print('Rust' in langs) # True
# Unpack
x, y = point
print(x, y) # 3 7
r, *rest = rgb
print(r, rest) # 255 [128, 0]
# Slice
print(langs[:2]) # ('Python', 'Rust')
# Convert
langs_list = list(langs) # → ['Python', 'Rust', 'Go']
langs_list.append('Java')
langs = tuple(langs_list) # → ('Python', 'Rust', 'Go', 'Java')
# Cannot mutate directly
try:
langs[0] = 'JavaScript'
except TypeError as e:
print(e) # 'tuple' object does not support item assignment- ✅ Reach for a tuple when the data is a fixed record (coords, RGB, DB row) that shouldn't change.
- ✅ Use tuples to return multiple values from functions — unpack at the call site.
- ✅ Use tuples as dictionary keys when you need a composite key (lists won't work — they're unhashable).
- ✅ Remember the trailing comma for one-item tuples:
(42,). - ✅ Convert with
list(t)andtuple(l)when you need to switch between mutability and immutability temporarily. - ✅ Document tuple structures with comments or
namedtuple/dataclassfor clarity — you'll meet these later.
- ❌
(42)is not a tuple. Use(42,)for a single-item tuple. - ❌ Trying to assign to or delete an index →
TypeError. Tuples are immutable. - ❌ Calling
tup.append(x)ortup.remove(x)→AttributeError. Those are list methods. - ❌ Forgetting that slicing returns a new tuple — the original is untouched (but it never could change anyway).
- ❌ Using a list where a tuple would be safer for fixed records — you lose the "don't change me" guarantee.
- ❌ Trying to use a list as a dictionary key — only tuples of hashable items work.
- What will be output by
print(developer[1])fordeveloper = ('Alice', 34, 'Rust Developer')? →34 - What is
desserts[1:3]fordesserts = ('cake', 'pie', 'cookies', 'ice cream')? →('pie', 'cookies') - What happens with
del developer[1]on a tuple? → You will get aTypeError.
- A tuple is an ordered, immutable, zero-indexed sequence — lists' frozen cousin.
- Create with
( ... )ortuple(iterable). One-item tuples need a trailing comma:(42,). - Supports indexing, negative indexing, slicing,
in, and unpacking — just like lists. - Cannot be modified after creation: assignment, deletion,
append,removeall error out. - Use tuples for fixed records, multiple return values, dictionary keys, and safer constants.
- Use lists when you need to grow or mutate the collection.
- Slicing returns a new tuple;
*restalways collects into a list. - Next up: tuple methods — the small but useful built-ins (
count(),index()) plus a closer look atkey=andreverse=in sorting.
➡️
Next chapter → Python Tuple Methods — count, index & sorted()
Tuples ship with just two read-only methods — count() and index(). Pair them with the built-in sorted(), plus its key and reverse options, to do real work with immutable sequences.