← Back to Core Python
Lesson 17 · Python Collections

Tuples

Fixed records, unpacking, and why the comma is what makes a tuple.

Beginner40 min

What you will be able to do

  • Create tuples correctly, including the empty and single-element cases
  • Index, slice, and iterate a tuple the same way you would a list
  • Explain what tuple immutability does and does not protect
  • Unpack a tuple into variables, including extended unpacking with *
  • Return several values from a function and unpack them at the call site
  • Use a tuple as a dictionary key, and say why a list cannot be one
  • Choose between a tuple and a list from what the data represents

The idea, in plain English

A tuple is an ordered collection, like a list, with one difference that changes everything about how it is used: you cannot change it after you make it. No append, no remove, no assigning to an index. What you built is what you have.

That sounds like a limitation and is mostly a signal. A list says "this collection will grow and shrink". A tuple says "these values belong together and the shape is fixed". (17.3850, 78.4867) is one location with two parts, not a collection of two numbers you might add a third to. Reading a tuple in someone else code tells you the shape is settled.

The feature you will use constantly is unpacking - pulling a tuple apart into named variables in one line. `name, score, course = student` is the whole of it, and once you see it you start noticing tuples everywhere: every function that returns two things, every loop over enumerate() or zip(), every row a database driver hands back.

Two things trip people up, and both are worth learning now rather than debugging later. The comma is what creates a tuple, not the brackets - so ("Python") is a plain string. And immutability is one level deep, exactly like the shallow copy in lesson 16: a tuple holding a list still lets you change that list.

Worked example: Reading database rows into named variables, one row at a time.

The comma makes the tuple

This is the rule that explains every confusing tuple result you will ever get. Parentheses group things - that is their job everywhere else in Python, as in (2 + 3) * 4. What creates a tuple is the comma.

So `numbers = 10, 20, 30` is a perfectly good tuple with no brackets at all, and `value = (10)` is just the integer 10 wrapped in redundant brackets. To make a one-element tuple you need the comma to do the work: `(10,)`.

The single-element case is where this actually bites, because it fails quietly. `roles = ("admin")` gives you the string "admin", so len(roles) is 5, `"a" in roles` is True, and looping over it yields five characters. Every one of those is a plausible-looking wrong answer.

Write the parentheses anyway. They are optional but they make the intent obvious, and they are required when a tuple appears inside a function call, where a bare comma would read as an argument separator.

What each of these actually is
(10)An int. The brackets only group - there is no comma.
(10,)A one-element tuple. The trailing comma is doing all the work.
10, 20, 30A three-element tuple. Parentheses are optional here.
()The empty tuple. The one case with no comma. tuple() is the same.
("Python")The string "Python" - a classic silent bug.
("Python",)A tuple holding one string.

Watch out: len(("Python")) is 6 and len(("Python",)) is 1. If a length looks like a character count, you have lost a comma.

Immutable means the structure, not the contents

A tuple will not let you replace, add, or remove an element. data[0] = 100 raises TypeError, and there is no append, no remove, no sort - those methods simply do not exist on a tuple.

What immutability actually fixes is the set of references the tuple holds. It does not reach inside them. If one of those references points at a list, that list is still a list and still mutable.

So with `data = ([1, 2], [3, 4])`, the assignment data[0] = [5, 6] fails, but data[0].append(3) succeeds and leaves you with ([1, 2, 3], [3, 4]). Nothing has gone wrong - the tuple still holds the same two list objects it always did. One of them just has different contents.

This is the same one-level rule as .copy() in lesson 16, and it matters for the same reason: a tuple of immutable values is genuinely fixed, while a tuple containing lists only looks that way.

Allowed and not allowed
data[0]Reading is always fine.
data[1:3]Slicing is fine - it returns a new tuple.
data + otherFine. Builds a new tuple; the originals are untouched.
data[0] = xTypeError. You cannot rebind an element.
data.append(x)AttributeError. Tuples have no mutating methods at all.
data[0].append(x)Works, if data[0] is a list. The tuple is unchanged - the list is not.

Unpacking, and the idioms built on it

Unpacking assigns each element of a tuple to its own name in one statement: `name, score, course = student`. The counts have to match exactly, or Python raises ValueError and tells you how many values it got.

Swapping two variables falls straight out of it. `a, b = b, a` works because Python builds the tuple (b, a) from the current values first, then unpacks it - so no temporary variable is needed and nothing is overwritten halfway through.

When the count is not fixed, a starred name absorbs the rest: `first, *middle, last = numbers` gives you the ends and everything between them. The starred name always becomes a list, even when it collects nothing, and you can have at most one of them.

The reason this matters more than it first appears is that Python hands you tuples constantly. enumerate() yields (index, value). zip() yields one tuple per position. A database driver yields one tuple per row. Every `for index, course in enumerate(courses)` you write is tuple unpacking.

Unpacking forms
a, b, c = dataExact match. Three values, three names, or ValueError.
a, b = b, aThe swap idiom. The right side is packed before anything is assigned.
first, *rest = dataHead and tail. rest is a list, and is [] if there is nothing left.
first, *middle, last = dataBoth ends. Needs at least two values.
*start, last = dataThe starred name can come first.
for name, score in pairs:Unpacking in a loop header - each element is taken apart as it arrives.

Tip: Use _ for a value you do not need: `_, name, _ = row` documents that you deliberately skipped two fields.

Returning several values, and reading rows

A Python function returns one object, but `return total, highest, lowest` packs three into a tuple on the way out, and the caller unpacks it on the way in. The parentheses are absent, so it does not look like a tuple at either end - but type(result) confirms it is one.

That is why multiple return values feel native in Python when they need a special mechanism in many other languages. There is no special mechanism. It is packing and unpacking.

The same shape covers rows of data. A database driver returns each row as a tuple, in column order, so `for user_id, name, email in rows:` is both the idiomatic way to read them and self-documenting about what each column is.

One caution as the tuple grows: past three or four fields, positions get hard to remember and a wrong order is a silent bug rather than an error. At that point reach for a dictionary, or later a dataclass or NamedTuple, where the fields have names.

Tuples as dictionary keys

A dictionary key has to be hashable, which in practice means its hash must never change while it is in use. If a key could change after insertion, the dictionary would be looking in the wrong place for it afterwards.

That rules lists out entirely - `{[1, 2]: "x"}` raises TypeError: unhashable type: list. A tuple of hashable values is fine, which makes it the standard way to key something by a combination of things: a coordinate pair, a (tenant_id, user_id) cache key, a (row, column) cell.

The rule follows the same one-level logic as everything else in this lesson. (1, 2) is hashable. ([1, 2], 3) is not, because it holds a list - a tuple is only as hashable as its contents.

Tip: A composite cache key is the everyday version of this: cache[(user_id, course_id)] needs no string formatting and cannot collide by accident.

Syntax and examples

Creating tuples - and the comma rule
courses = ("Python", "React", "Node.js") print(courses[0]) # Python print(len(courses)) # 3 # Parentheses are optional - the comma is what matters numbers = 10, 20, 30 print(numbers) # (10, 20, 30) print(type(numbers)) # <class 'tuple'> # Mixed types are fine, as in a list data = (100, "Python", True, 10.5) # The single-element trap value = (10) print(type(value)) # <class 'int'> <- not a tuple value = (10,) print(type(value)) # <class 'tuple'> # The same trap with a string, which is worse because it looks plausible role = ("admin") print(len(role)) # 5 <- characters role = ("admin",) print(len(role)) # 1 <- one element # Empty tuples - the one case that needs no comma empty = () print(type(empty), len(empty)) # <class 'tuple'> 0 print(tuple() == ()) # True
Indexing, slicing, and the operators
courses = ("Python", "React", "Node.js") print(courses[0]) # Python print(courses[-1]) # Node.js - counting back from the end numbers = (10, 20, 30, 40, 50) print(numbers[1:4]) # (20, 30, 40) - stop is excluded print(numbers[:3]) # (10, 20, 30) print(numbers[2:]) # (30, 40, 50) print(numbers[::2]) # (10, 30, 50) - every second one print(numbers[::-1]) # (50, 40, 30, 20, 10) - a new, reversed tuple # Every slice gives back a tuple, never a list print(type(numbers[1:4])) # <class 'tuple'> # + and * build new tuples, exactly as they do for lists print((1, 2) + (3, 4)) # (1, 2, 3, 4) print((1, 2) * 3) # (1, 2, 1, 2, 1, 2) # Membership and comparison work the same way too print("Python" in courses) # True print("Java" not in courses) # True print((1, 2, 3) == (1, 2, 3)) # True print((1, 2, 3) == (3, 2, 1)) # False - order counts # The only two methods a tuple has numbers = (10, 20, 10, 30, 10) print(numbers.count(10)) # 3 print(numbers.index(20)) # 1 # numbers.index(99) -> ValueError
What immutability does and does not stop
courses = ("Python", "React", "Node.js") # courses[1] = "Angular" -> TypeError: does not support item assignment # courses.append("Go") -> AttributeError: no attribute 'append' # courses.remove("React") -> AttributeError: no attribute 'remove' # To change one, build a new one courses = courses + ("Go",) # note the comma - ("Go") would be a string print(courses) # ('Python', 'React', 'Node.js', 'Go') # Or convert, edit, convert back editable = list(courses) editable.append("Rust") courses = tuple(editable) print(courses) # Immutability is one level deep data = ([1, 2], [3, 4]) # data[0] = [10, 20] -> TypeError: the tuple will not rebind data[0].append(3) # but the list inside is still a list print(data) # ([1, 2, 3], [3, 4]) # A tuple of immutable values really is fixed all the way down settings = ("localhost", 5432, True)
Unpacking, swapping, and the starred name
student = ("Chandu", 90, "Python") name, score, course = student print(name, score, course) # Chandu 90 Python # The counts must match exactly data = (10, 20, 30) # a, b = data -> ValueError: too many values to unpack # a, b, c, d = data -> ValueError: not enough values to unpack # Swapping, with no temporary variable a, b = 10, 20 a, b = b, a print(a, b) # 20 10 # The right side is packed into (20, 10) first, then unpacked. # Extended unpacking - the starred name is always a list numbers = (1, 2, 3, 4, 5) first, *middle, last = numbers print(first, middle, last) # 1 [2, 3, 4] 5 first, second, *remaining = (1, 2, 3, 4, 5, 6) print(remaining) # [3, 4, 5, 6] *beginning, second_last, last = (1, 2, 3, 4, 5) print(beginning) # [1, 2, 3] # It collects an empty list quite happily first, *remaining = (1,) print(first, remaining) # 1 [] # Skip what you do not need _, name, _ = ("ST101", "Ravi", 85) print(name) # Ravi
The worked example - multiple returns and database rows
def calculate_scores(scores): return sum(scores), max(scores), min(scores) total, highest, lowest = calculate_scores([80, 90, 75, 95]) print(total, highest, lowest) # 340 95 75 # There is no special mechanism here - it is just a tuple result = calculate_scores([80, 90, 75, 95]) print(result) # (340, 95, 75) print(type(result)) # <class 'tuple'> # A status-plus-data return def authenticate(token): if token == "valid": return True, "Authentication successful" return False, "Token rejected" success, message = authenticate("valid") print(success, message) # True Authentication successful # Rows come back as tuples, in column order rows = [ (1, "Ravi", "ravi@example.com"), (2, "Anita", "anita@example.com"), ] for user_id, name, email in rows: print(user_id, name, email) # 1 Ravi ravi@example.com # 2 Anita anita@example.com # enumerate and zip hand you tuples too - you have been unpacking all along courses = ["Python", "React"] print(list(enumerate(courses))) # [(0, 'Python'), (1, 'React')] for index, course in enumerate(courses, start=1): print(index, course) # 1 Python / 2 React names = ["Ravi", "Anita", "Chandu"] scores = [85, 92, 88] for name, score in zip(names, scores): print(name, score)
Converting, and tuples as keys
# Any iterable converts print(tuple(["Python", "React"])) # ('Python', 'React') print(list(("Python", "React"))) # ['Python', 'React'] print(tuple("Python")) # ('P', 'y', 't', 'h', 'o', 'n') print(tuple(range(1, 6))) # (1, 2, 3, 4, 5) # A tuple key is stable, so a dictionary can rely on it locations = { (17.3850, 78.4867): "Hyderabad", (12.9716, 77.5946): "Bengaluru", } print(locations[(17.3850, 78.4867)]) # Hyderabad # A list cannot be a key, because it could change after insertion # locations[[17.3850, 78.4867]] = "x" -> TypeError: unhashable type: 'list' # A tuple is only as hashable as what it holds print(hash((1, 2))) # fine # hash(([1, 2], 3)) -> TypeError: unhashable type: 'list' # The everyday use - a composite cache key cache = {} cache[(101, "PY101")] = "enrolled" print(cache[(101, "PY101")]) # enrolled # Nested tuples index twice data = (("Ravi", 85), ("Anita", 92)) print(data[0]) # ('Ravi', 85) print(data[0][1]) # 85

Tip: A tuple in a function call needs its parentheses: f((1, 2)) passes one tuple, while f(1, 2) passes two arguments.

Watch out: A trailing comma after a single value turns it into a tuple, even by accident. `total = count,` makes a one-element tuple, which is a real and confusing bug.

Everything a tuple supports

All of it is read-only. Anything that would change the tuple simply does not exist.

items[i]

Index, from 0. Negative counts back from the end.

courses[-1]
items[a:b:c]

Slice. Always returns a new tuple.

numbers[::-1]
len(items)

How many elements.

len(courses)
in / not in

Membership. Scans the tuple, so O(n).

"Python" in courses
+

Concatenate into a new tuple. Tuple on both sides.

(1, 2) + (3,)
*

Repeat the elements into a new tuple.

(1, 2) * 3
==, <, >

Same rules as lists - order matters, comparison is lexicographic.

(1, 2) < (1, 3)
count(x)

How many elements equal x.

numbers.count(10)
index(x)

Position of the first match. ValueError if absent.

courses.index("React")
tuple(x) / list(x)

Convert either way. Used to edit a tuple and rebuild it.

tuple(["a", "b"])
hash(items)

Works only if every element is itself hashable.

hash((1, 2))

List against tuple

The honest one-line version: a list is a collection you expect to change, a tuple is a record whose shape is fixed.

Mutable

List yes, tuple no. This is the only real difference; everything else follows from it.

Ordered, indexed, sliceable

Both. Duplicates are allowed in both.

append, remove, sort, reverse

List only. A tuple has no mutating methods.

count, index

Both.

Usable as a dictionary key

Tuple only, and only when its contents are hashable. Never a list.

Typical use

List for a collection that grows; tuple for a fixed record, a coordinate, a multiple return.

Signals to the reader

"This will change" against "this shape is settled". That signal is most of the value.

Try it yourself

The code does not change. Swap the content string and the program does something else entirely.

Lose the comma on purpose

“Set role = ("admin") and print type(role), len(role), and loop over it. Then add the comma and do the same. Say out loud why the first version printed five things.”

Test how deep immutability goes

“Build data = ([1, 2], [3, 4]). Try data[0] = [9, 9], then try data[0].append(9). Explain why one raises and the other does not.”

Unpack badly

“Try a, b = (10, 20, 30) and read the error message carefully - it tells you exactly how many values it found. Then fix it two ways: with three names, and with a starred name.”

Watch the swap happen

“Swap two variables with a, b = b, a. Then try to do it in two separate statements (a = b, then b = a) and work out why that version loses a value.”

Confirm the hidden tuples

“Print list(enumerate(["a", "b"])) and list(zip([1, 2], ["x", "y"])). Look at the brackets in the output and name what is inside the list.”

What usually goes wrong

Forgetting the comma in a one-element tuple

Parentheses group; the comma creates the tuple. Without it you get whatever was inside - and if that is a string, it will iterate, have a length, and support `in`, so nothing complains.

✗ roles = ("admin")
print(len(roles))     # 5  - it is a string
✓ roles = ("admin",)
print(len(roles))     # 1  - a tuple
Trying to modify a tuple

There is no append, remove, or item assignment. If the data needs to change, either it should have been a list, or you build a new tuple.

✗ courses = ("Python", "React")
courses.append("Node.js")
# AttributeError: no attribute 'append'
✓ courses = courses + ("Node.js",)
# or, for several edits:
editable = list(courses)
editable.append("Node.js")
courses = tuple(editable)
Assuming a tuple is immutable all the way down

The tuple fixes which objects it holds, not what those objects contain. A tuple of lists is not a frozen structure.

✗ data = ([1, 2], [3, 4])
data[0].append(5)
print(data)   # ([1, 2, 5], [3, 4]) - it changed
✓ data = ((1, 2), (3, 4))
# now nothing inside can change either
Unpacking the wrong number of values

The counts must match exactly. ValueError is raised at the moment of assignment, and the message tells you how many values were actually there.

✗ data = (10, 20, 30)
a, b = data
# ValueError: too many values to unpack (expected 2)
✓ a, b, c = data
# or, when the length varies:
a, *rest = data
Using a list as a dictionary key

A key must hash to the same value for as long as it is in the dictionary. A list could change, so Python refuses it outright.

✗ coordinates = [17.3850, 78.4867]
locations = {coordinates: "Hyderabad"}
# TypeError: unhashable type: 'list'
✓ coordinates = (17.3850, 78.4867)
locations = {coordinates: "Hyderabad"}
Reaching for a tuple just because it is shorter to type

The choice should come from the data. If the collection will grow or shrink, a tuple only means you will convert it to a list later and back again.

✗ students = ("Ravi",)
students = students + ("Anita",)   # rebuilding on every add
✓ students = ["Ravi"]
students.append("Anita")

Best practices

  • Write the parentheses even when they are optional - they show the reader that a tuple was intended.
  • Never leave a single-element tuple without its trailing comma, and read length errors as a missing-comma clue.
  • Choose a tuple when the number of fields is fixed and the values belong together; choose a list when the collection changes.
  • Unpack into named variables rather than indexing by position - name, score = student beats student[0] and student[1].
  • Use _ for fields you are deliberately ignoring, so the reader knows it was a choice.
  • Past three or four fields, move to a dictionary, a NamedTuple, or a dataclass - positional meaning stops being readable.
  • Use a tuple for a composite dictionary key instead of joining values into a string.
  • If you need a fully frozen structure, make sure the elements are immutable too.

Practice

Write these yourself before opening anything. Getting them wrong first is most of how this sticks.

1.

Create a tuple of ("Python", "JavaScript", "TypeScript", "React"). Print the first element, the last element, the length, the tuple reversed, and whether "Python" is in it.

Show hint

Negative indexing gives you the last element; a slice with a step of -1 reverses.

Show solution
languages = ("Python", "JavaScript", "TypeScript", "React") print(languages[0]) # Python print(languages[-1]) # React print(len(languages)) # 4 print(languages[::-1]) # ('React', 'TypeScript', 'JavaScript', 'Python') print("Python" in languages) # True
2.

Unpack student = ("Ravi", 90, "Python") into name, score, and course, then print all three.

Show hint

One assignment statement, three names on the left.

Show solution
student = ("Ravi", 90, "Python") name, score, course = student print(name) # Ravi print(score) # 90 print(course) # Python
3.

From numbers = (10, 20, 30, 40, 50), use extended unpacking to get first = 10, middle = [20, 30, 40], and last = 50.

Show hint

One starred name between two ordinary ones.

Show solution
numbers = (10, 20, 30, 40, 50) first, *middle, last = numbers print(first) # 10 print(middle) # [20, 30, 40] <- a list, not a tuple print(last) # 50
4.

Write calculate(a, b) returning the sum, the difference, and the product. Call it with 10 and 5 and unpack the three results.

Show hint

Return the three values separated by commas - Python packs them for you.

Show solution
def calculate(a, b): return a + b, a - b, a * b total, difference, product = calculate(10, 5) print(total) # 15 print(difference) # 5 print(product) # 50 print(type(calculate(10, 5))) # <class 'tuple'>
5.

Convert courses = ["Python", "React", "FastAPI"] to a tuple, then try to modify it and observe the error.

Show hint

tuple() converts. Then attempt an item assignment and read what Python says.

Show solution
courses = ["Python", "React", "FastAPI"] frozen = tuple(courses) print(frozen) # ('Python', 'React', 'FastAPI') try: frozen[0] = "Django" except TypeError as error: print("TypeError:", error) # TypeError: 'tuple' object does not support item assignment # The original list is untouched and still editable courses.append("Flask") print(courses)
6.

Loop over points = ((10, 20), (30, 40), (50, 60)) with tuple unpacking and print "Point: x=10, y=20" for each.

Show hint

Unpack in the for statement itself, not inside the body.

Show solution
points = ((10, 20), (30, 40), (50, 60)) for x, y in points: print(f"Point: x={x}, y={y}") # Point: x=10, y=20 # Point: x=30, y=40 # Point: x=50, y=60
Coding challenge

Student records, read only

A fixed set of student results arrives as a tuple of tuples. Report on it using unpacking throughout, without converting the records to lists.

It should
  • Print each student name and score on its own line, using unpacking in the for statement.
  • Find the highest score, and print the name of the student who got it.
  • Print the course catalogue below in the format "PY101 - Core Python - Beginner".
  • Prove the records cannot be modified by attempting one change and catching the error.
  • Build a lookup dictionary keyed by a tuple of (course_id, level), and read one value back out of it.
students = ( ("Ravi", 85), ("Anita", 92), ("Chandu", 88), ("Priya", 95), ) courses = ( ("PY101", "Core Python", "Beginner"), ("PY201", "Advanced Python", "Intermediate"), ("FA101", "FastAPI", "Intermediate"), ) # 1. Name and score on each line # 2. The highest score, and who got it # 3. The catalogue lines # 4. Prove it is immutable # 5. A dictionary keyed by (course_id, level)
Show one solution
students = ( ("Ravi", 85), ("Anita", 92), ("Chandu", 88), ("Priya", 95), ) courses = ( ("PY101", "Core Python", "Beginner"), ("PY201", "Advanced Python", "Intermediate"), ("FA101", "FastAPI", "Intermediate"), ) # 1. Unpack in the for statement - each record comes apart as it arrives for name, score in students: print(f"{name}: {score}") # 2. max() with a key, then unpack the winning record. # student[1] inside the key is the score. top_name, top_score = max(students, key=lambda student: student[1]) print(f"Highest: {top_name} with {top_score}") # Highest: Priya with 95 # 3. Three fields, three names for course_id, title, level in courses: print(f"{course_id} - {title} - {level}") # PY101 - Core Python - Beginner # PY201 - Advanced Python - Intermediate # FA101 - FastAPI - Intermediate # 4. The tuple will not rebind an element try: students[0] = ("Ravi", 100) except TypeError as error: print("TypeError:", error) # TypeError: 'tuple' object does not support item assignment # 5. A composite key - immutable, so the dictionary can rely on it by_id_and_level = {} for course_id, title, level in courses: by_id_and_level[(course_id, level)] = title print(by_id_and_level[("FA101", "Intermediate")]) # FastAPI # A list key would have been refused here: # by_id_and_level[[course_id, level]] = title # TypeError: unhashable type: 'list'

Key points

  • The comma creates a tuple, not the parentheses - (10) is an int and (10,) is a tuple.
  • The empty tuple () is the one case that needs no comma.
  • Indexing, slicing, +, *, in, and comparison all work exactly as they do on lists, and slices return tuples.
  • A tuple has only two methods: count() and index(). Nothing that would change it exists.
  • Immutability is one level deep - a tuple holding a list still lets that list change.
  • Unpacking assigns elements to names in one line, and the counts must match or you get ValueError.
  • A starred name collects the rest and is always a list, possibly empty.
  • a, b = b, a swaps without a temporary, because the right side is packed before anything is assigned.
  • Returning several values is just packing and unpacking - the result really is a tuple.
  • A tuple of hashable values can be a dictionary key; a list can never be one.
  • Choose a tuple for a fixed record, a list for a collection that changes.

Quick check before you move on

data = (10, 20, 30); a, b, c = data - what does print(b) show?
20. Unpacking assigns left to right, so b takes the second element.
data = (1, 2, 3, 4, 5); first, *middle, last = data - what are the three values?
1, [2, 3, 4], and 5. The starred name is always a list, even though the source was a tuple.
a = 10; b = 20; a, b = b, a - what are a and b afterwards?
20 and 10. Python builds (20, 10) from the current values first, then unpacks it, so nothing is overwritten mid-swap.
What is type(("Python",)) and what is type(("Python"))?
tuple and str. The trailing comma in the first one is the only difference and the only thing that matters.
data = ([1, 2], [3, 4]); data[0].append(5) - what is data, and why is this allowed?
([1, 2, 5], [3, 4]). The tuple still holds the same two list objects; immutability stops you rebinding an element, not changing what an element contains.
What happens with a, b = (10, 20, 30)?
ValueError: too many values to unpack (expected 2). Unpacking needs an exact match unless a starred name is present.
Why does {[1, 2]: "x"} fail while {(1, 2): "x"} works?
Dictionary keys must be hashable. A list is mutable, so its hash could change after insertion and the dictionary could no longer find it. A tuple of hashable values is stable.

Interview questions

What is a tuple?

An ordered, immutable sequence. It supports indexing, slicing, iteration, and membership like a list, but once created its elements cannot be replaced, added to, or removed.

What is the difference between a list and a tuple?

Mutability, and everything follows from it. A list can be changed and so has append, remove, and sort; a tuple cannot and so has only count and index. A tuple can be a dictionary key when its contents are hashable; a list never can.

How do you create a single-element tuple, and why is that the syntax?

With a trailing comma: (10,). Parentheses only group in Python - the comma is the tuple constructor. (10) is just the integer inside redundant brackets.

Can a tuple contain mutable objects, and what does that mean for immutability?

Yes. The tuple fixes which objects it references, not their contents. ([1, 2], [3, 4]) will not let you rebind an element but will let you append to the inner list, so a tuple is only truly frozen when its elements are immutable too.

What is extended unpacking?

Using a starred name to absorb an arbitrary number of elements: first, *middle, last = values. The starred name becomes a list, there can be at most one per assignment, and it may collect nothing.

What actually happens when a function returns multiple values?

Nothing special. return a, b packs the values into a tuple and returns that one object; the caller unpacks it. type() on the result confirms it is a tuple.

Why can a tuple be a dictionary key when a list cannot?

A key must hash consistently for as long as it is stored. Lists are mutable, so their hash could change after insertion and the dictionary could no longer locate the entry - Python makes them unhashable rather than allow that. A tuple of hashable values cannot change, so it is safe.

How does a, b = b, a work without a temporary variable?

The right-hand side is evaluated first and packed into a tuple from the current values, then that tuple is unpacked into the left-hand names. Both original values are already captured before any assignment happens.

Is a tuple faster or smaller than a list?

Slightly smaller, and constructing a constant one is faster because CPython can build it at compile time. The difference rarely matters - choose based on whether the data should be able to change, not on micro-performance.

When would you use a tuple over a list in real code?

For a fixed record such as a coordinate or a database row, for returning several values, for a constant sequence such as allowed roles, and for a composite dictionary key. Anywhere the collection grows or shrinks, use a list.

What are the limits of using tuples as records?

Fields are positional, so past three or four values the meaning becomes hard to read and a swapped order is a silent bug rather than an error. At that point a NamedTuple, dataclass, or dictionary gives the fields names.

Where do tuples show up without you creating them?

enumerate() yields (index, value) pairs, zip() yields one tuple per position, database drivers return rows as tuples, and multiple return values are tuples. Most tuple unpacking people write is against tuples they never constructed.

Quiz

  1. 1.

    How do you add an element to a tuple?

  2. 2.

    Which methods does a tuple have?

  3. 3.

    What type is the starred variable in first, *rest = data?

  4. 4.

    Does slicing a tuple give you a list?

  5. 5.

    When is a tuple not hashable?

Comments

Sign in to leave a comment. Your name and photo come from Google; nothing else is shared.

Loading comments...