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 assignment

How 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.5946

How 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
Bengaluru

How 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 5

How 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.83

How 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}
2

How 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 True

How 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
Bengaluru

How 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.75

Unpacking 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.0

The 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') 25

Each 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 *rest collects 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 namedtuple when 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.

Newsletter

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