| Type | Ordered | Mutable | Duplicates | Typical use |
|---|---|---|---|---|
list | Yes | Yes | Yes | A general-purpose, ordered sequence |
tuple | Yes | No | Yes | A fixed, immutable record — e.g. (x, y) coordinates |
set | No | Yes | No | Fast membership checks, deduplication |
dict | Insertion order (3.7+) | Yes | Keys unique | Key-value lookups |
The interview-relevant distinction is list vs. tuple: choosing a tuple isn't just style — it signals "this shouldn't change," and it's what lets a tuple be used as a dict key or set member (since those require hashable, immutable values — a list can't be either).
point = (3, 4) # tuple — immutable, hashable
seen_points = {(3, 4), (1, 2)} # a set of tuples — this works
seen_points = {[3, 4]} # TypeError: unhashable type: 'list'
Knowing why a set or dict lookup is fast (average O(1), backed by a hash table) versus a list's O(n) linear scan is one of the most commonly probed pieces of Python knowledge in interviews:
| Operation | list | dict / set |
|---|---|---|
x in collection | O(n) | O(1) average |
| Append / add | O(1) amortized | O(1) average |
| Insert at index 0 | O(n) — shifts everything | N/A |
| Lookup by key/index | O(1) by index, O(n) by value | O(1) average by key |
That single row — x in collection — is the reason "reach for a set when you only need membership testing" shows up in almost every "how would you optimize this" interview follow-up. A list of 10,000 items scanned repeatedly inside a loop turns an O(n) check into an O(n²) algorithm; swapping it for a set fixes the asymptotic complexity with no other code changes.
collections — the standard library's specialized containersThe four built-ins above cover most code, but the collections module is worth knowing for the cases they don't fit well:
from collections import defaultdict, Counter, deque, namedtuple
# defaultdict — no more "if key not in dict: dict[key] = []"
groups = defaultdict(list)
for name, team in [("Ana", "A"), ("Bo", "B"), ("Cy", "A")]:
groups[name_team := team].append(name) # groups["A"] auto-creates [] on first access
# Counter — frequency counting in one line
counts = Counter("mississippi") # Counter({'i': 4, 's': 4, 'p': 2, 'm': 1})
counts.most_common(2) # [('i', 4), ('s', 4)]
# deque — O(1) appends/pops from BOTH ends, unlike a list (O(n) from the front)
queue = deque([1, 2, 3])
queue.appendleft(0) # deque([0, 1, 2, 3]) — a plain list.insert(0, x) is O(n)
# namedtuple — a lightweight, immutable record with named fields
Point = namedtuple("Point", ["x", "y"])
p = Point(3, 4)
p.x, p.y # (3, 4) — reads like an object, behaves like a tuple
deque is the one to remember for interview questions specifically: a plain list used as a queue (list.pop(0)) is O(n) per operation because every remaining element has to shift down one index; a deque makes both ends O(1).