Choosing between list, set and dict in Python is not a matter of taste once you ask three questions in order: does the order matter, must the items be unique, and will you look them up by name? Each container answers those three questions differently, and the right one makes the rest of your code shorter.
What questions should you ask when picking a container?
Work through them in this order, because each answer narrows the field.
- Do I need to look items up by name? If yes, use a dictionary. The lookup becomes a single operation instead of a search.
- Must every item appear exactly once? If yes, use a set, which enforces it for you.
- Does the order of the items matter? If yes, use a list or a tuple rather than a set, which has no order.
- Do I need to change the contents later? If no, a tuple is lighter and safer than a list.
Most real problems need more than one of these structures at once, and that is fine. A catalogue is the standard example: a list of records for the order, a dictionary keyed by id for the lookup, and a set of tags for the membership questions.
courses = [
{"id": "py-101", "title": "Python Basics", "tags": ["python", "beginner"]},
{"id": "js-201", "title": "JavaScript Deep Dive", "tags": ["javascript", "web"]},
{"id": "py-201", "title": "Python Data Structures", "tags": ["python", "data"]},
]
by_id = {course["id"]: course for course in courses}
print(by_id["js-201"]["title"])
all_tags = set()
for course in courses:
all_tags.update(course["tags"])
print(sorted(all_tags))
print("python" in all_tags)
One structure per question. The list keeps the catalogue in a deliberate order, the dictionary answers "which course is this id", and the set answers "do we offer anything tagged python" without walking the catalogue.
How do list, set and dict differ?
| Type | Ordered | Unique | Mutable | Lookup by key | Typical job |
|---|---|---|---|---|---|
list | yes | no | yes | no | a sequence of similar items |
tuple | yes | no | no | no | a fixed record |
set | no | yes | yes | no | membership and set maths |
frozenset | no | yes | no | no | a hashable set, usable as a key |
dict | yes | keys only | yes | yes | named values |
deque | yes | no | yes | no | a queue with cheap ends |
Counter | yes | keys only | yes | yes | counting occurrences |
The "Unique" column is the subtle one. A list happily holds the same value twice. A set cannot. A dictionary allows only one entry per key but lets the same value appear under several keys, which is exactly what you want when two students share a grade.
When is a list the wrong choice?
Two situations. The first is when you keep asking whether something is present. Every one of those questions walks the list, so a program that checks membership thousands of times against a large list spends all its time searching.
allowed = {"ada", "grace", "alan"}
for user in ["ada", "bot", "grace"]:
print(user, "allowed" if user in allowed else "blocked")
Building the membership collection as a set turns each of those questions into a lookup that does not depend on how many items there are.
The second situation is when the items are records you keep addressing by name. A list of dictionaries works, but every access becomes courses[3]["title"], and the 3 is a piece of information that has to be kept in step with the list. A dictionary keyed by id removes the possibility of the two drifting apart.
Switching is not always free either. Converting a list to a dictionary costs a pass over the data, and a dictionary uses more memory per item than a list does. For a handful of records, keep the list and index it. For thousands of records you will look up repeatedly, build the dictionary once and index from then on.
When is a dict the wrong choice?
When the data is really a sequence and you have forced names onto it. A list of three coordinates is a list. Turning it into {"x": 1, "y": 2, "z": 3} adds ceremony and loses the ability to unpack it as x, y, z = point.
A dict is also the wrong choice when you need duplicates. If two rows can legitimately have the same identifier, keying by that identifier throws one of them away. That is the classic bug in a system where the identifier is supposed to be unique but the data does not guarantee it.
And a dict is wrong when you need a set of values with no names attached. {"a", "b"} says less than {"a": True, "b": True}, and it does the same job faster.
seen = {"ada", "grace"}
print(seen)
named = {"ada": True, "grace": True}
print(named)
What specialised variants should you keep in mind?
The standard library ships several containers that are pre-tuned versions of the basics, and reaching for them saves code.
| Instead of | Use | Why |
|---|---|---|
| a dict you build by hand to count | collections.Counter | counts in one line, returns zero for unknown keys |
| a dict of lists you fill up | collections.defaultdict(list) | creates the empty container on demand |
| a list you push to and pop from | collections.deque | cheap operations at both ends |
| a tuple with named fields | dataclass or namedtuple | self-documenting fields, still immutable |
| a set used as a dictionary key | frozenset | hashable, so it can be a key |
from collections import Counter
courses = [
{"tags": ["python", "beginner"]},
{"tags": ["python", "data"]},
]
counts = Counter()
for course in courses:
counts.update(course["tags"])
print(counts.most_common())
Notice that Counter.update() takes a list of values and adds one to each, which saves the loop body you would otherwise write. The same trick works with defaultdict: hand it a factory and it manufactures the empty container whenever you ask for a key that is not there yet.
Decision guide
Work down this list and stop at the first match.
- Named values I will look up by key — dictionary.
- Duplicate-free membership testing, order irrelevant — set.
- An ordered sequence of similar items — list.
- A fixed shape that never changes — tuple, or a dataclass if the fields deserve names.
- Repeatedly adding and removing from both ends — deque.
- Counting how often each value appears — Counter.
Try it yourself: take a list of ten blog posts, each with an id, a title, a list of tags and a status. Build a dictionary keyed by id, a set of all tags, and a Counter of tag frequencies from that same list. Then deliberately store two posts under the same id in a dictionary and watch one of them disappear — that is the failure mode that tells you a dictionary was the wrong structure. Finish by writing one sentence per structure explaining which question it answers.