Tuples & sets
Group fixed records with tuples and track uniqueness with sets.
Why tuples and sets earn their own types
A list is your default container, but two jobs deserve a sharper tool. When a group of values forms one fixed record (a (latitude, longitude) pair, one database row, the several results a function returns), a tuple signals "this shape will not change." When you only care whether something is present or how many distinct things you saw (unique user IDs in a log, an allow-list of permitted roles), a set answers in one fast step instead of a scan.
Tuples: fixed records
A tuple is an ordered, immutable sequence. You index it like a list, but you cannot reassign, append to, or grow it after creation:
point = (3, 4)
point[0] # 3
x, y = point # unpack: x = 3, y = 4
That unpacking is why functions return tuples to hand back several values at once. divmod(17, 5) returns (3, 2), and you can catch it as q, r = divmod(17, 5). A tuple of hashable values is itself hashable, so tuples can live inside a set or serve as dict keys (lists cannot).
Sets: a hash table of unique keys
A set is an unordered collection of unique, hashable values, backed by the same hash table that powers dict keys. Duplicates collapse on the way in, and membership is answered by hashing, not scanning:
seen = {1, 2, 2, 3} # stored as {1, 2, 3}
3 in seen # True
len(set([1, 2, 2, 3])) # 3 distinct count
Wrapping a list in set(...) is the idiomatic way to drop duplicates or count distinct values.
When to use which
tuple: a small fixed record whose fields will not change.set: you care about uniqueness or membership, not order or position.
Braces and parentheses are overloaded in Python, and the literal you write is not always the type you get:
| You write | You get | Watch out for |
|---|---|---|
| [1, 2] | list | mutable, so it can never go inside a set |
| (1, 2) | tuple | immutable and hashable, so it can |
| (3) | the int 3, not a tuple | a one-element tuple needs the comma: (3,) |
| {1, 2} | set | unordered; my_set[0] raises TypeError |
| {} | an empty dict, not a set | use set() when you want an empty set |
| {a: 1} with real quotes on the key | dict | braces mean dict the moment a colon appears |
Pitfalls
- Empty braces
{}make an emptydict, not aset. Useset()for an empty set. - A one-element tuple needs a trailing comma.
(3)is just the integer3;(3,)is a tuple. - Sets are unordered. Never rely on iteration order or index a set (
my_set[0]raisesTypeError). If you need order, sort into a list. - Set elements must be hashable, so a
setoflists fails, but asetoftuples works.
Interview nuance: membership cost is the reason to reach for a set. x in some_list is O(n) because Python checks each element in turn, while x in some_set is O(1) on average because it hashes straight to a bucket. That is exactly why counting distinct values through a set beats comparing every pair, and why de-duplication loops that build a set as they go run in linear time.
nums = [1, 2, 2, 3, 3, 3]
print(len(set(nums))) # 3 distinct values
print(2 in set(nums)) # True
point = (3, 4)
x, y = point
print(x, y) # 3 4Apply
Your turn
The task this lesson builds to.
Implement unique_count(arr): return how many distinct values are in the list arr.
For [1, 2, 2, 3] return 3.
2 hints and 4 automated checks are waiting in the workspace.
Practice
Make it stick
A second problem on the same idea, so it survives past today.
Implement min_max(arr): return a tuple (smallest, largest) of the list arr.
For [3, 1, 5, 2] return (1, 5).
2 hints and 4 automated checks are waiting in the workspace.