Given a new data problem, how do you decide whether to use a list, dict, or set?
Choose a list when order matters and you need indexed access or duplicates. Choose a dict when you need to map keys to values and look up by key in O(1). Choose a set when you need uniqueness, fast membership testing, or set-algebra operations. Getting this choice wrong usually means either incorrect results (keeping duplicates when you needed uniqueness) or avoidable O(n) lookups.
How to think about it
This is a design question, not a syntax one. The interviewer wants to hear you reason from access patterns — how the data will actually be used — rather than recite each type’s features. Start from one question, “what do I need to do with this data?”, and let the answer pick the structure.
| Question | If yes |
|---|---|
| Need order or positional access? | list |
| Need to keep duplicates? | list |
| Need to look up by a key? | dict |
| Need to attach extra data to each key? | dict |
| Only need uniqueness, membership, or set algebra? | set |
| Need iteration in insertion order? | list, or dict (ordered since 3.7) |
The performance stakes are real: a list answers x in data by scanning every element — O(n) — while a set or dict answers it with a single hash lookup — O(1). Same result, wildly different cost as the data grows.
A worked example
# A set gives O(1) membership — the same answer a list would give, far cheaper
valid = {"USD", "EUR", "GBP", "JPY"}
print("'EUR' valid?", "EUR" in valid)
print("'INR' valid?", "INR" in valid)
# Deduplicate a stream while preserving first-seen order: the set does the
# O(1) "have I seen this?" check, the list remembers the order
seen, unique = set(), []
for x in [3, 1, 3, 2, 1, 4]:
if x not in seen:
seen.add(x)
unique.append(x)
print("First-seen order:", unique)
# A dict is a set of keys that also carries a value per key
user_profiles = {1: "Alice", 2: "Bob", 42: "Carol"}
print("Lookup user 42:", user_profiles[42]) # O(1), no scan
# Set algebra, for free
a = {1, 2, 3, 4, 5}
b = {3, 4, 5, 6, 7}
print("Union :", a | b)
print("Intersection:", a & b)
print("Difference :", a - b)
'EUR' valid? True
'INR' valid? False
First-seen order: [3, 1, 2, 4]
Lookup user 42: Carol
Union : {1, 2, 3, 4, 5, 6, 7}
Intersection: {3, 4, 5}
Difference : {1, 2}
The dedup loop is the pattern worth memorising: a set and a list working together — the set answers “seen it?” in O(1), the list remembers the order the set throws away.
The single highest-leverage swap
Turning a list-used-as-a-lookup into a set is the most impactful one-line change in most data pipelines:
# O(n) per check — a linear scan every single time
valid_codes = ["USD", "EUR", "GBP", "JPY"]
if code in valid_codes: ...
# O(1) per check — a hash lookup
valid_codes = {"USD", "EUR", "GBP", "JPY"}
if code in valid_codes: ...
When you need lookup and order
Use a dict — since 3.7 it preserves insertion order — or collections.OrderedDict if you want the intent spelled out. For sorted-key access, sortedcontainers.SortedDict gives O(log n) operations.