All 57 lessons
Lesson 13 of 57 · Data Structures
Tuples and Unpacking
By Skillancy Editorial Team
Run the code in the free Python Compiler or on your own computer. Lessons that read files use the FreshBasket practice data: download the dataset (ZIP).
A tuple looks like a list but with one decisive difference: once created, it cannot be changed. That sounds like a limitation, but it is a feature. A tuple says to the reader and to Python, "these values belong together and will not change". Coordinates, an (order id, amount) pair, a date broken into year, month and day: these are natural tuples.
Tuples are used constantly behind the scenes. When a function returns two values, it actually returns a tuple. When you write for city, orders in data, each row is unpacked from a tuple. Pandas returns shapes as tuples, zip produces tuples, and dictionary items() gives tuples. This short lesson shows you how to recognise and use them confidently.
Creating tuples
point = (12.97, 77.59) # parentheses and commas
same_point = 12.97, 77.59 # the commas make the tuple; brackets are optional
single = ("Pune",) # one element needs a trailing comma
empty = ()It is the commas, not the brackets, that create a tuple. ("Pune") is just a string in brackets, while ("Pune",) is a tuple with one element. Tuples support indexing, slicing, len, in, count and index, exactly like lists, but they have no append, remove or sort, because they cannot change.
Worked examples
Example 1: Creating and reading a tuple
Question: Store the location of the Bengaluru dark store and read its parts.
store = ("S01", "Bengaluru", 12.9716, 77.5946)
print(store[0], store[1])
print(store[-2:])
print(len(store))
print(type(store))Output
S01 Bengaluru
(12.9716, 77.5946)
4
<class 'tuple'>How it works: The tuple holds an id, a city and two coordinates. Indexing and slicing work as they do for lists, and slicing a tuple gives another tuple.
Reading the result: The store S01 is in Bengaluru and the tuple has 4 fields. Slicing the last two gives the coordinates as a new tuple (12.9716, 77.5946).
Example 2: Immutability
Question: What happens if I try to change a tuple?
store = ("S01", "Bengaluru", 12.9716, 77.5946)
store[1] = "Bangalore"Output
Traceback (most recent call last):
File "example.py", line 2, in <module>
store[1] = "Bangalore"
~~~~~^^^
TypeError: 'tuple' object does not support item assignmentHow it works: Tuples do not allow item assignment, so Python raises a TypeError. If you really need a changed version, create a new tuple: for example store = store[:1] + ("Bangalore",) + store[2:].
Reading the result: The error text says the tuple object does not support item assignment. This protection is valuable: a tuple passed to a function cannot be altered behind your back.
Example 3: Unpacking into variables
Question: Pull the four fields out into separate variables.
store = ("S01", "Bengaluru", 12.9716, 77.5946)
store_id, city, lat, lon = store
print(store_id, city)
print(lat, lon)Output
S01 Bengaluru
12.9716 77.5946How it works: Unpacking assigns the elements to names on the left, one by one, in order. The number of names must match the number of elements, or Python raises a ValueError. The same works for lists and any other iterable.
Reading the result: The variables now hold S01, Bengaluru, 12.9716 and 77.5946, which is far more readable than store[2] and store[3] scattered through your code.
Example 4: Swapping and ignoring values
Question: Swap two variables, and ignore the parts of a tuple you do not need.
a, b = 10, 25
a, b = b, a
print(a, b)
_, city, _, _ = ("S01", "Bengaluru", 12.9716, 77.5946)
print(city)Output
25 10
BengaluruHow it works: The right side is packed into a temporary tuple (25, 10), and then unpacked into a and b, which swaps them without a helper variable. By convention the name _ marks a value you do not need.
Reading the result: After the swap a is 25 and b is 10, and we extracted only the city, Bengaluru.
Example 5: Star unpacking
Question: A feedback record has a date, an order id and then any number of tags. Separate them.
record = ("2025-12-12", "O04344", "late", "refund", "call-back")
date, order_id, *tags = record
print(date, order_id)
print(tags)
first, *_, last = (1, 2, 3, 4, 5)
print(first, last)Output
2025-12-12 O04344
['late', 'refund', 'call-back']
1 5How it works: The starred name collects all remaining items into a list. This is a clean way to handle records with a variable number of trailing fields. Only one starred name is allowed per assignment.
Reading the result: tags holds 3 items: ['late', 'refund', 'call-back']. Notice that the collected part is a list even though the source was a tuple.
Example 6: Returning multiple values
Question: Write a function that returns the minimum, maximum and average of a list in one call.
def summarise(values):
return min(values), max(values), sum(values) / len(values)
times = [34, 22, 41, 19, 27, 30]
result = summarise(times)
print(result, type(result))
low, high, avg = summarise(times)
print(low, high, round(avg, 2))Output
(19, 41, 28.833333333333332) <class 'tuple'>
19 41 28.83How it works: return a, b, c packs the three values into a tuple. The caller can keep the tuple whole (result) or unpack it straight into three variables. Python does not need a special mechanism for "multiple return values": it is just a tuple.
Reading the result: The function returned a tuple of 3 items. After unpacking, low is 19, high is 41 and the average is 28.83. We will write many functions like this in the next module.
Example 7: Tuples as dictionary keys and set members
Question: Count orders by (city, payment method) pairs.
orders = [("Pune", "UPI"), ("Pune", "COD"), ("Delhi", "UPI"), ("Pune", "UPI"), ("Delhi", "UPI")]
counts = {}
for key in orders:
counts[key] = counts.get(key, 0) + 1
print(counts)
print(counts[("Pune", "UPI")])Output
{('Pune', 'UPI'): 2, ('Pune', 'COD'): 1, ('Delhi', 'UPI'): 2}
2How it works: Dictionary keys must be unchangeable, so a tuple works but a list does not. A pair of values acts as a compound key. counts.get(key, 0) returns the existing count, or 0 if the key is new, and we add one.
Reading the result: The pair ('Pune', 'UPI') occurred 2 times. Trying to use a list as a key would raise TypeError: unhashable type: 'list'. Pandas does the same thing for you when you group by two columns.
Example 8: Comparing and sorting tuples
Question: Sort stores by city, then by orders, using tuples.
stores = [("Pune", 611), ("Delhi", 1291), ("Delhi", 1000), ("Bengaluru", 1301)]
print(sorted(stores))
print(sorted(stores, key=lambda s: s[1], reverse=True))
print((1, 5) < (2, 0), (1, 5) < (1, 9))Output
[('Bengaluru', 1301), ('Delhi', 1000), ('Delhi', 1291), ('Pune', 611)]
[('Bengaluru', 1301), ('Delhi', 1291), ('Delhi', 1000), ('Pune', 611)]
True TrueHow it works: Tuples compare element by element from the left: the first elements decide, and only if they tie does Python look at the second. So sorted(stores) orders by city alphabetically and then by orders. The key=lambda s: s[1] form sorts by the second element instead (a lambda is a tiny unnamed function, explained in the functions module).
Reading the result: The default sort lists Bengaluru, then the two Delhi stores in ascending order of orders, then Pune. The key-based sort puts the biggest store first: ('Bengaluru', 1301). Comparing (1, 5) < (1, 9) is true because the first items tie and 5 is less than 9.
Example 9: namedtuple for readable records
Question: Make a record where fields have names instead of positions.
from collections import namedtuple
Store = namedtuple("Store", ["store_id", "city", "orders"])
s = Store("S01", "Bengaluru", 1301)
print(s)
print(s.city, s.orders)
print(s[2])
city_name = s._asdict()["city"]
print(city_name)Output
Store(store_id='S01', city='Bengaluru', orders=1301)
Bengaluru 1301
1301
BengaluruHow it works: namedtuple builds a small class where each position also has a name. It still behaves like a tuple (indexing, unpacking, immutability) but s.city is much clearer than s[1]. _asdict() converts it to a dictionary-like object.
Reading the result: We can read Bengaluru by name or by position, and the object prints with its field names. For records with behaviour, the dataclasses introduced in the OOP lesson are a more modern choice, but namedtuple is still common in older code and libraries.
Example 10: Tuple or list?
Question: How do I decide?
import sys
coords_tuple = (12.97, 77.59)
coords_list = [12.97, 77.59]
print(sys.getsizeof(coords_tuple) < sys.getsizeof(coords_list))
print(hash(coords_tuple) is not None)
try:
hash(coords_list)
except TypeError as e:
print("list:", e)Output
True
True
list: unhashable type: 'list'How it works: Tuples take slightly less memory and can be hashed, which is why they can be dictionary keys. Lists cannot be hashed because their contents can change. The try/except block catches the error so we can display its message.
Reading the result: The first line confirms the tuple is smaller, and the last shows that lists are unhashable. As a rule of thumb: use a tuple for a fixed record of mixed values (a row, a point, a pair), and a list for a growing collection of similar items (all delivery times).
Common mistakes
1. Forgetting the comma in a one-element tuple. ("Pune") is a string.
print(type(("Pune")), type(("Pune",)))Output
<class 'str'> <class 'tuple'>2. Mismatched number of names when unpacking.
a, b = (1, 2, 3)Output
Traceback (most recent call last):
File "example.py", line 1, in <module>
a, b = (1, 2, 3)
^^^^
ValueError: too many values to unpack (expected 2)3. Trying to change a tuple. Convert to a list, change it, then convert back, or build a new tuple.
4. Thinking an immutable tuple is always fully frozen. A tuple that contains a list still lets you change the list inside it: t = ([1], 2) then t[0].append(5) works.
5. Using a tuple where you need to add items later. If the collection grows, use a list.
Practice questions
Practice 1 (Easy): Unpack an order
Unpack order = ("O02299", "C0078", 612.75) into oid, cust and amount, then print them in a sentence.
Show solution
order = ("O02299", "C0078", 612.75)
oid, cust, amount = order
print(f"Order {oid} by {cust} was worth Rs. {amount}")Output
Order O02299 by C0078 was worth Rs. 612.75Unpacking names each field, so the f-string reads naturally.
Practice 2 (Medium): Min and max together
Write a function spread(values) that returns the range (max minus min) and the midpoint (average of max and min). Call it for [19, 27, 30, 41] and unpack the answer.
Show solution
def spread(values):
low, high = min(values), max(values)
return high - low, (high + low) / 2
width, mid = spread([19, 27, 30, 41])
print(width, mid)Output
22 30.0The function packs two results into a tuple, and the caller unpacks them into width (22) and mid (30.0). Note the tuple unpacking used inside the function too.
Practice 3 (Hard): Group by compound key
From these (store, slot, orders) records, total the orders for each (store, slot) key and print them sorted by key:
[("S01", "Morning", 40), ("S01", "Evening", 70), ("S02", "Morning", 25), ("S01", "Morning", 15)]
Show solution
records = [("S01", "Morning", 40), ("S01", "Evening", 70), ("S02", "Morning", 25), ("S01", "Morning", 15)]
totals = {}
for store, slot, n in records:
key = (store, slot)
totals[key] = totals.get(key, 0) + n
for key in sorted(totals):
print(key, totals[key])Output
('S01', 'Evening') 70
('S01', 'Morning') 55
('S02', 'Morning') 25Each record is unpacked in the for line. The pair (store, slot) is a hashable compound key. Sorting the keys sorts first by store and then by slot, and the S01 morning total is 55.
Key takeaways
- A tuple is an ordered, immutable sequence; it is the commas that create it, and a single-item tuple needs a trailing comma.
- Unpacking (
a, b, c = record) names the fields of a tuple, and*restcollects leftovers into a list. - Functions return multiple values as a tuple, which callers can unpack immediately.
- Tuples are hashable when their contents are, so they work as dictionary keys and set members, unlike lists.
- Tuples compare element by element, so sorting a list of tuples sorts by the first field, then the second.
- Use tuples for fixed records and lists for growing collections; use
namedtuplewhen field names would help.
Frequently asked questions
Why use a tuple if a list can do everything it does?
Because immutability communicates intent, protects data from accidental changes, allows tuples to be used as dictionary keys and set members, and uses slightly less memory. It also makes code easier to reason about.
Is a tuple really unchangeable?
The tuple itself, yes: you cannot add, remove or replace items. But if one of its items is a mutable object such as a list, that inner object can still change.
Can I convert between lists and tuples?
Yes: tuple(my_list) and list(my_tuple). This is how you "edit" a tuple: convert to a list, change it, convert back.
What does the underscore mean as a variable name?
By convention _ signals "a value I do not care about". It is a normal variable name, so nothing special happens, but readers and tools understand your intent.
Do I need parentheses to create a tuple?
Only when the context would otherwise be ambiguous, such as inside a function call. x = 1, 2 is valid. Many people add brackets anyway for clarity.
What is a hashable object?
An object whose value never changes during its life and can be reduced to a number (its hash). Strings, numbers and tuples of hashables qualify; lists, dictionaries and sets do not. Only hashable objects can be dictionary keys or set members.
Keep learning Python
One email a week with Python and analytics guides, new practice questions and cohort dates. No spam, unsubscribe anytime.
Ready to practise? Try the Python interview questions