Collections: lists, tuples, sets and dicts
The four built-in containers and the one question that picks between them: what does this data need to be cheap at?
You can represent almost anything with four containers, and choosing well is most of what separates fast code from slow code. The decision is rarely about what the data is and almost always about which operations must be cheap: order, membership, uniqueness, or lookup by key.
Ask what has to be fast
A list is a row of slots: fast to index, slow to search. A tuple is a frozen row. A set is a bag of unique items with near-instant membership. A dict maps keys to values with near-instant lookup. Pick by the operation you will run most, not by what the data looks like.
The explorer below stores the same additions in each container. Add duplicates and watch ordering, uniqueness and lookup cost diverge.
Add the same values to each container and watch ordering, duplicates and lookup change
list: ordered, duplicates allowed, 0-indexed
A list answers membership by checking one element at a time; a set or dict jumps straight to a bucket. The cost is hidden in the container choice, which is why "is this value present?" is cheap on a set and expensive on a list even when both hold the same items.
The four containers
- A list
[1, 2, 3]is ordered and mutable.appendis amortised ; membership and search are because they scan. - A tuple
(1, 2, 3)is ordered and immutable. That immutability makes tuples usable as dictionary keys and signals "this record does not change". - A set
{1, 2, 3}is unordered and holds each element once. Membership is on average because it hashes into buckets rather than scanning. - A dict
{"a": 1, "b": 2}maps unique keys to values, also with average lookup. Since Python 3.7 it preserves insertion order, but that is a bonus, not the reason to use it.
Building the table
Membership is where the difference bites. x in my_list walks the list one
element at a time — . x in my_set hashes x, jumps to one bucket, and
answers in on average. Converting a list to a set before a loop of
membership tests is one of the most common rewrites for speed:
seen = set()
for word in document:
if word in seen: # O(1), not O(n)
continue
seen.add(word)Sharing, copying and mutation
Assignment never copies a container; it binds another name to the same object.
a = [1, 2, 3]
b = a # same object
b.append(4) # a is now [1, 2, 3, 4] too
c = a[:] # a shallow copy; c is independentBecause lists and dicts are mutable, passing one into a function lets that function change your data. Tuples and strings are immutable, so they cannot be changed in place — which is exactly why they are safer to share.
Union and intersection are set operations, not list operations
a | b and a & b combine and intersect sets. On lists the same symbols
mean something else entirely (| is a TypeError for lists). When the
question is "which items appear in both?", convert to sets first.
Illustrative vs real
The explorer uses single-character items and a handful of operations so the
panel stays readable. Real containers hold arbitrary objects, nest inside one
another, and are built with comprehensions such as [x * 2 for x in row].
The cost model — indexed , scanned , hashed average — is
the same at any size, and only becomes visible at scale.
Check yourself
Eduspheria wiki · Programming & Data Structures, Python foundations
0 / 5 answered
From the exam paper
Modeled on NITJ AI-503, End-Sem December 2024
0 / 5 answered
Where next: functions — naming a block of work, giving it inputs, and keeping its variables local.