Tuples
3. Tuples
A tuple is an ordered, immutable sequence.
They are similar to lists, but the key difference is: a list is mutable, a tuple is immutable
Quick Reference: Tuple methods
| Method | Purpose | Returns | Complexity | Notes |
|---|---|---|---|---|
count(x) | Count occurrences of value | int | O(n) | Returns 0 if not found |
index(x) | Find first matching value | Index | O(n) | Raises ValueError if not found |
Quick Reference: Common tuple operations
| Operation | Purpose | Returns | Complexity | Notes |
|---|---|---|---|---|
tuple(iterable) | Create tuple from iterable | New tuple | O(n) | Very common conversion |
(x,) | Create single-element tuple | Tuple | O(1) | Comma is required |
t[i] | Get item by index | Element | O(1) | Negative indexing supported |
t[a:b] | Slice tuple | New tuple | O(k) | k = slice size |
len(t) | Number of elements | int | O(1) | Very common in interviews |
x in t | Membership check | bool | O(n) | Linear scan |
for x in t | Iterate through elements | Iterator behavior | O(n) total | Common iteration pattern |
enumerate(t) | Iterate with index + value | Iterator | O(n) total | Common in loops |
t1 + t2 | Concatenate tuples | New tuple | O(n + m) | Creates new object |
t * n | Repeat tuple | New tuple | O(n¡k) | Copies references |
min(t) | Smallest value | Element | O(n) | Built-in |
max(t) | Largest value | Element | O(n) | Built-in |
sum(t) | Sum numeric values | Number | O(n) | Built-in |
a, b = t | Tuple unpacking | Variables | O(n) | Very common in practice |
a, *rest = t | Extended unpacking | Variables | O(n) | Useful for flexible assignment |
hash(t) | Hash immutable tuple | int | O(n) | Only if all elements are hashable |
t1 < t2 | Lexicographical comparison | bool | O(n) | Compared left to right |
Example
point = (10, 20)
This means two important things:
Ordered
Every element has a stable position.
point[0] # 10
point[1] # 20
Just like lists.
Immutable
Unlike lists, tuples cannot be changed after creation.
This will fail:
point[0] = 99 # TypeError: 'tuple' object does not support item assignment
This is the defining feature of tuples.
1.3.2 Syntax
Standard syntax
coords = (4, 7)
Interview trap: single-element tuple
x = (5) # Is just an integer in parentheses.
x = (5,) # Is is single element tuple
1.3.3 Why tuples exist
Why use tuple instead of list? / Why do tuples exist?
This is not an academic question. Tuples solve real problems.
1. Fixed-size structure
Tuples communicate that the data will not grow.
# A point is always (x, y) - never grows to (x, y, z)
point = (10, 20)
# Using a list would suggest it might change:
point = [10, 20] # Looks like you might .append() later
Why this matters:
- API contracts: function says "I return a 3-tuple" = caller knows exactly what to expect
- prevents bugs: if function accidentally appends, static analysis could catch tuple type error
- semantic clarity: tuple = "fixed record", list = "growing collection"
2. Immutability prevents bugs
Tuples cannot be modified, so accidental mutations are impossible.
def get_rgb():
return (255, 0, 0)
r, g, b = get_rgb()
r = 100 # This creates a NEW variable, doesn't modify the tuple
# Compare with list:
def get_rgb_list():
return [255, 0, 0]
colors = get_rgb_list()
colors[0] = 100 # Modifies the list - may affect other code pointing to it
Why this matters:
- shared references: if multiple variables reference the same list and one modifies it, all see the change
- thread safety: immutable objects are inherently safe in multi-threaded code
- caching: immutable tuples can be cached by Python (small tuples are cached), mutable lists cannot
3. Hashable = can be dictionary keys or set members
Lists cannot be dictionary keys because they're mutable. Tuples can (if contents are hashable).
# This works - tuple as key
cache = {
(10, 20): "point A",
(30, 40): "point B"
}
# This crashes - list not hashable
cache = {
[10, 20]: "point A" # TypeError: unhashable type: 'list'
}
Why this matters:
- deduplication:
set(points)with tuples removes duplicates instantly. Can't do this with lists - fast lookup: use tuple coordinates as dictionary keys for O(1) position lookup in graphs
- caching positions: track visited nodes in graph algorithms using a set of tuples
Real example:
visited = set() # Track visited grid positions
visited.add((0, 0))
if (1, 2) in visited: # O(1) lookup
...
4. Safer semantic meaning
A tuple literally says "this data is a fixed record" to other developers.
def get_user():
return ("Alice", 30, "alice@example.com") # Tuple = fixed record, 3 specific fields
Compare to:
def get_user():
return ["Alice", 30, "alice@example.com"] # List = collection, might grow
Why this matters:
- code readability: tuple communicates intent at a glance
- prevents feature creep: "we return a 2-tuple for performance" is a contract
- API stability: Python type checkers (mypy) will error if you return wrong tuple size
5. Can be used with multiple return unpacking
Tuples enable elegant destructuring at the call site.
# Tuple return enables this clean unpacking:
def get_stats(nums):
return min(nums), max(nums), len(nums)
minimum, maximum, count = get_stats([1, 2, 3])
Why this matters:
- readability: immediate clarity what's being returned
- type safety: mypy knows exactly 3 values returned
- ergonomics: unpacking is more readable than indexing
[0],[1],[2]
Real-world example: Why tuples matter
# Coordinate-based cache using tuple as key
class Cache:
def __init__(self):
self.data = {} # Maps coordinate tuples to cached results
def get(self, x, y):
# Can use (x, y) as key because tuples are hashable
return self.data.get((x, y))
def set(self, x, y, value):
self.data[(x, y)] = value
def is_cached(self, x, y):
# Can use 'in' operator because key is hashable
return (x, y) in self.data
cache = Cache()
cache.set(10, 20, "result")
if cache.is_cached(10, 20): # O(1) lookup
print(cache.get(10, 20))
With a list, none of this works because lists aren't hashable.
Strong answer summary:
- fixed-size: communicates API contract
- immutable: prevents accidental mutations and safer in multi-threaded code
- hashable: enables use as dict key or set member
- semantic clarity: tuple = fixed record, list = growing collection
- enables unpacking: clean destructuring syntax
1.3.4 Tuple unpacking
This is one of the most important tuple topics.
Basic unpacking
point = (10, 20)
x, y = point
Now:
x = 10
y = 20
This is called unpacking.
Python assigns each element to a variable.
Why this is important
Tuple unpacking appears everywhere in real code.
Example
name, age = ("Ruben", 30)
Very common.
Function return unpacking
This is one of the biggest tuple use cases.
def get_user():
return "Ruben", 30
name, age = get_user()
This is extremely common in Python.
Technically the function returns a tuple.
1.3.5 Swapping variables
A very famous Python tuple pattern.
a = 1
b = 2
a, b = b, a
Now:
a = 2
b = 1
This is elegant and highly interview-relevant.
Behind the scenes, Python uses tuple packing and unpacking.
1.3.6 Extended unpacking
Medior-level topic and often asked.
nums = (1, 2, 3, 4)
first, *middle, last = nums
Result
first = 1
middle = [2, 3]
last = 4
Very important to know.
Notice:
middle becomes a list, not a tuple.
That is an important subtlety.
1.3.7 Immutability in depth
This is the key concept.
Once created, the tuple structure cannot change.
You cannot:
- assign by index
- append
- remove
- sort in place
Example
point.append(3) # AttributeError
Important nuance: nested mutables
This is a strong interview nuance.
A tuple itself is immutable, but it may contain mutable objects.
Example
data = ([1, 2], [3, 4])
data[0].append(99) # ([1, 2, 99], [3, 4])
Important insight:
- the tuple structure is immutable
- the objects inside may still be mutable
1.3.8 Hashability and dictionary keys
This is extremely important.
Tuples can be dictionary keys if all elements are hashable.
Example
locations = {
(10, 20): "home"
}
This is valid.
Why list cannot be key
{
[10, 20]: "home"
}
This fails because lists are mutable and not hashable.
This comparison is frequently tested.
1.3.9 Important tuple methods
Tuples have fewer methods than lists because they are immutable.
count()
Count occurrences of a value.
t = (1, 2, 2, 3, 2)
count = t.count(2) # 3
What it does
Counts how many times a value appears in the tuple.
colors = ("red", "blue", "red")
colors.count("red") # 2
Complexity
O(n): Must scan the entire tuple.
index()
Returns the index of the first occurrence of the value.
t = (10, 20, 30, 20)
pos = t.index(20) # 1
Raises error if not found
t = (1, 2, 3)
t.index(5) # ValueError: tuple.index(x): x not in tuple
Complexity
O(n): Must search from the beginning.
Why tuples have few methods
Tuples are intentionally minimal because they are immutable.
You can't add/remove/modify elements, so methods like append(), remove(), pop(), sort() don't make sense.
This is by design â immutability = fewer operations = clearer intent.
1.3.10 Namedtuples
Namedtuples are a way to create lightweight, immutable objects with named fields instead of positional indexing.
They combine the best of tuples (immutable, hashable) with the clarity of named attributes.
What is a namedtuple?
A namedtuple is created using collections.namedtuple() and behaves like a tuple but with named fields.
from collections import namedtuple
Point = namedtuple('Point', ['x', 'y'])
p = Point(10, 20)
print(p[0]) # 10 (positional access still works)
print(p.x) # 10 (named access is clearer)
print(p.y) # 20
Why use namedtuples?
Regular tuples are hard to read:
user = ("Alice", 30, "alice@example.com")
print(user[0]) # Is this name? Age? Email? Unclear.
print(user[1])
print(user[2])
Namedtuples are self-documenting:
User = namedtuple('User', ['name', 'age', 'email'])
user = User("Alice", 30, "alice@example.com")
print(user.name) # Clear: this is the name
print(user.age) # Clear: this is the age
print(user.email) # Clear: this is the email
Creating namedtuples
Syntax 1: List of field names
Point = namedtuple('Point', ['x', 'y'])
Syntax 2: Space-separated string
Point = namedtuple('Point', 'x y')
Both are equivalent.
Using namedtuples
from collections import namedtuple
Point = namedtuple('Point', ['x', 'y'])
p1 = Point(0, 0)
p2 = Point(3, 4)
# Access by name
print(p1.x, p1.y) # 0 0
# Tuple unpacking still works
x, y = p1
print(x, y) # 0 0
# Still immutable
p1.x = 5 # AttributeError: can't set attribute
Namedtuples are still tuples
They retain all tuple benefits:
Still hashable (can be dict keys, set members):
cache = {
Point(0, 0): "origin",
Point(1, 2): "point A"
}
Still immutable:
p = Point(10, 20)
p[0] = 99 # TypeError: 'Point' object does not support item assignment
Can be unpacked:
x, y = Point(10, 20)
Real-world example: Coordinate system
Without namedtuple (hard to read):
points = [(0, 0), (1, 0), (1, 1), (0, 1)]
for point in points:
print(f"x={point[0]}, y={point[1]}") # What are [0] and [1]?
With namedtuple (self-documenting):
from collections import namedtuple
Point = namedtuple('Point', ['x', 'y'])
points = [Point(0, 0), Point(1, 0), Point(1, 1), Point(0, 1)]
for point in points:
print(f"x={point.x}, y={point.y}") # Clear and readable
Real-world example: Function returns
Without namedtuple:
def get_user():
return ("Alice", 30, "alice@example.com")
name, age, email = get_user()
It works, but ("Alice", 30, "alice@example.com") is cryptic. What does each field mean?
With namedtuple:
from collections import namedtuple
User = namedtuple('User', ['name', 'age', 'email'])
def get_user():
return User("Alice", 30, "alice@example.com")
user = get_user()
print(user.name, user.age, user.email) # Self-documenting
When to use namedtuples
Use namedtuples when:
- You have a fixed-size structure with labeled fields (like a coordinate, a user record, a database row)
- You want immutability and hashability (dict keys, set members)
- You want readability (named fields instead of magic indexing)
- You want something lightweight (namedtuples are just tuples with names)
Use regular tuples when:
- You just need a simple lightweight return value and clarity is obvious from context
- You need variable-length sequences (tuples can be any size, namedtuples are fixed)
Use dataclasses when:
- You need mutable objects with named fields
- You need more complex behavior (methods, defaults, validation)
1.3.11 Verbal interview questions
Answer these out loud:
- Why use tuple instead of list?
- Why can tuple be a dictionary key?
- Explain unpacking
- Explain extended unpacking
- How can a tuple still contain mutable data?
- What is a namedtuple and why would you use it?
1.3.12 Coding drills
Drill 1: swap values
def swap(a, b):
...
Use tuple unpacking.
Drill 2: return min and max
def min_max(nums: list[int]) -> tuple[int, int]:
...
Return both values as a tuple.
Drill 3: coordinate map
points = {
(0, 0): "origin",
(1, 2): "point"
}
Explain why tuple works as key.
1.3.10 Common interview problems
Tuples appear frequently in coding interviews, especially in graph problems, coordinate problems, and when working with multiple return values.
Problem 1: Merge Intervals with Tuples
Question: Given a list of tuples representing intervals (start, end), merge overlapping intervals.
def merge(intervals: list[tuple[int, int]]) -> list[tuple[int, int]]:
# intervals = [(1, 3), (2, 6), (8, 10), (15, 18)]
# Return [(1, 6), (8, 10), (15, 18)]
...
Visual: [(1, 3), (2, 6)] overlap â merge to [(1, 6)]
Key insight: Sort by start time, iterate and merge when overlapping.
Problem 2: Multiple Return Values
Question: A function needs to return min, max, and count from a list. Design this using tuples.
def stats(nums: list[int]) -> tuple[int, int, int]:
# nums = [5, 1, 9, 3]
# Return (1, 9, 4) # min, max, count
...
Key insight: Return tuple, caller unpacks with min_val, max_val, count = stats(nums)
Problem 3: Graph Coordinates
Question: Given a 2D grid, find a path from start to end. Use tuples for coordinates.
def find_path(grid: list[list[int]], start: tuple[int, int], end: tuple[int, int]) -> list[tuple[int, int]]:
# start = (0, 0), end = (3, 3)
# Return [(0, 0), (0, 1), (0, 2), (1, 2), (2, 2), (3, 2), (3, 3)]
...
Key insight: Use tuples as coordinate keysâcan add to sets or use as dict keys to track visited nodes.
visited = set()
visited.add((0, 0))
if (0, 1) not in visited:
...
Problem 4: Sorting with Tuple Keys
Question: Sort a list of people by age, then by name (use tuple as sort key).
def sort_people(people: list[dict]) -> list[dict]:
# people = [
# {"name": "Alice", "age": 30},
# {"name": "Bob", "age": 25},
# {"name": "Carol", "age": 30}
# ]
# Return sorted by age, then name
...
Key insight: Use tuple as sort key: sorted(people, key=lambda p: (p["age"], p["name"]))
Problem 5: Two Sum with Tuples
Question: Find two numbers in a list that sum to a target. Return as tuple (index1, index2).
def twoSum(nums: list[int], target: int) -> tuple[int, int]:
# nums = [2, 7, 11, 15], target = 9
# Return (0, 1) # indices of 2 and 7
...
Key insight: Use dictionary to store valueâindex mapping, then check for complement.
num_map = {}
for i, num in enumerate(nums):
complement = target - num
if complement in num_map:
return (num_map[complement], i)
num_map[num] = i
Problem 6: Tuple as Dictionary Key
Question: Given a matrix of cities and distances, use tuples as dictionary keys to store distances efficiently.
def build_distance_map(cities: list[str], distances: list[tuple[str, str, int]]) -> dict:
# distances = [("NYC", "LA", 2800), ("LA", "NYC", 2800), ...]
# Return {("NYC", "LA"): 2800, ...}
...
Key insight: Tuples as keys because they're hashable and immutable.
dist_map = {}
for city1, city2, distance in distances:
dist_map[(city1, city2)] = distance
Problem 7: Unpacking Function Returns
Question: A function returns division result and remainder. Unpack these values.
def divmod_custom(a: int, b: int) -> tuple[int, int]:
# a = 17, b = 5
# Return (3, 2) # quotient, remainder
...
q, r = divmod_custom(17, 5) # Unpacking
Key insight: Tuple return â destructuring assignment at call site.
Problem 8: Extended Unpacking
Question: Given a tuple of coordinates, extract first point, middle points, and last point.
def analyze_path(path: tuple[tuple[int, int], ...]) -> tuple:
# path = ((0, 0), (1, 1), (2, 2), (3, 3), (4, 4))
# first, *middle, last = path
# Return first, middle list, last
...
Key insight: Extended unpacking with *middle to capture variable-length sequences.
Problem 9: Swap Without Temporary Variable
Question: Swap two variables using tuple unpacking.
def swap(a: int, b: int) -> tuple[int, int]:
a, b = b, a
return (a, b)
Why it works: Python creates a tuple (b, a), then unpacks to a, b.
Problem 10: Namedtuples for Clarity
Question: Instead of plain tuples, use namedtuple for better readability in a coordinate system.
from collections import namedtuple
Point = namedtuple('Point', ['x', 'y'])
def distance(p1: Point, p2: Point) -> float:
# p1 = Point(x=0, y=0), p2 = Point(x=3, y=4)
# Return 5.0 (3-4-5 triangle)
return ((p1.x - p2.x)**2 + (p1.y - p2.y)**2)**0.5
Key insight: namedtuple provides named fields instead of positional indexing. Still immutable and hashable, but more readable.