Three containers, three different contracts
Python gives you three built-in containers that beginners often use interchangeably:
list, tuple, and set. They look superficially similar — you can loop
over all three — but they make fundamentally different promises about mutability,
ordering, and lookup speed. Picking the wrong one leads to subtle bugs, slow code, and
instant red flags in interviews. This guide untangles each contract and gives you a
decision rule that makes the right choice obvious.
Quick-reference comparison
| Property | list | tuple | set |
|---|---|---|---|
| Mutable? | Yes | No | Yes (elements must be immutable) |
| Ordered? | Yes | Yes | No |
| Allows duplicates? | Yes | Yes | No — silently deduplicates |
| Indexable? | Yes (a[0]) | Yes (t[0]) | No |
| Membership test | O(n) | O(n) | O(1) average |
| Hashable? | No | Yes (if all elements are) | No |
| Can be dict key? | No | Yes | No |
| Typical use | Growing collection | Fixed record | Membership / dedup |
| Literal syntax | [1, 2, 3] | (1, 2, 3) or 1, 2, 3 | {1, 2, 3} |
list — the mutable, ordered sequence
A list holds a dynamic sequence of references to any Python objects, in the order
you add them. Use it when the collection needs to grow, shrink, or be reordered.
scores = [90, 85, 70]
scores.append(60) # mutate freely
scores.sort() # [60, 70, 85, 90]
scores[0] = 55 # reassign any element
# lists allow duplicates and mixed types
items = [1, "hello", 1, None] # valid
The most common interview trap: appending to the front (insert(0, x)) is O(n)
because every element shifts right. Use collections.deque for fast front insertions.
Rule of thumb: reach for a list when you have a homogeneous, changing collection
that you'll add to, sort, or filter — the default when you're unsure.
Deep dive: Lists & Slicing interview questions
tuple — the immutable record
A tuple is an immutable, fixed-length sequence. Its slots can't be reassigned after
creation, which makes it the right structure for data that shouldn't change — coordinates,
database rows, function return values, dictionary keys.
point = (3.0, 4.0) # fixed record — a 2D coordinate
x, y = point # tuple unpacking (clean, idiomatic)
# tuples as dict keys — works because they're hashable
distances = {(0, 0): 0, (3, 4): 5.0}
# immutability is shallow — a tuple of lists is mutable inside
t = ([1, 2], [3, 4])
t[0].append(99) # allowed — mutates the list, not the tuple slot
t[0] = [] # TypeError — can't reassign the slot itself
Because all elements must be hashable for the tuple to be hashable, (1, [2, 3]) raises
TypeError on hash() — a classic gotcha.
Rule of thumb: reach for a tuple when the data is a fixed bundle that won't
change — if you'd describe it as "a record" or "a point", it's a tuple.
Deep dive: Tuples & Named Tuples interview questions
set — the unordered, deduplicated membership checker
A set is a hash table of unique values with no defined order. Its superpower is
O(1) average membership testing — regardless of how many elements are in the set,
x in s costs the same.
seen = set()
seen.add(1)
seen.add(2)
seen.add(1) # silently ignored — sets store unique elements only
print(seen) # {1, 2} (order not guaranteed)
# O(1) membership vs O(n) for lists
big_list = list(range(1_000_000))
big_set = set(big_list)
999_999 in big_list # scans up to 1 million elements
999_999 in big_set # one hash lookup
# fast deduplication
names = ["alice", "bob", "alice", "carol"]
unique = list(set(names)) # ["alice", "bob", "carol"] — order lost
Sets also support the classic mathematical operations:
a = {1, 2, 3}
b = {2, 3, 4}
a | b # {1, 2, 3, 4} union
a & b # {2, 3} intersection
a - b # {1} difference
a ^ b # {1, 4} symmetric difference
Because sets use hashing internally, only hashable types can be elements — you can't
have a set of lists. Use frozenset when you need an immutable, hashable set (e.g., as a
dict key).
Rule of thumb: reach for a set when the question is "is X in this collection?" or
"give me the unique items" — never when order or duplicates matter.
Deep dive: Sets & Frozensets interview questions
What about dict?
Dictionaries belong in this conversation too. A dict is essentially a set of unique
keys, each mapped to a value. Since Python 3.7, insertion order is preserved. Use it
when you need a key → value mapping, not just membership.
# prefer dict when you need to look up a value, not just check presence
cache = {}
cache["user_42"] = {"name": "Alice", "age": 30}
cache.get("user_99", None) # safe lookup, no KeyError
Deep dive: Dictionaries interview questions
The decision rule
Answer three questions in order:
- Do I need key → value mapping? →
dict - Will the collection change, or do I need ordering / indexing?
- Yes, needs to change or be indexed →
list - No, it's a fixed record →
tuple
- Yes, needs to change or be indexed →
- Do I only need to know "is X a member?" or "what are the unique values?" →
set
# Q1: mapping needed? → dict
user = {"name": "Alice", "age": 30}
# Q2a: changing collection? → list
queue = ["task_a", "task_b"]
queue.append("task_c")
# Q2b: fixed record? → tuple
rgb = (255, 128, 0) # color — never reassigns channels
# Q3: membership / unique? → set
visited_urls = set()
if url not in visited_urls:
visited_urls.add(url)
Recap
A list is mutable, ordered, and allows duplicates — the default workhorse for
growing collections. A tuple is immutable, ordered, hashable, and faster — use it
for fixed records and dictionary keys. A set is unordered, deduplicated, and offers
O(1) membership — reach for it whenever you need fast "is X in here?" or unique-values
logic. When you need key-value pairs, add dict to the toolkit. The deciding factor is
usually mutability first, then whether order or uniqueness matters more.