Choosing the right structure: a decision guide
You now have four structures and, most of the time, more than one would work. This lesson is about picking well — because the choice shapes how readable your code is, and occasionally whether it finishes this week.
The short version
| You need | Use |
|---|---|
| An ordered collection you will add to and remove from | list |
| A fixed grouping whose shape will not change | tuple |
| To look things up by name or id | dictionary |
| Unique items, and fast "is it in there?" | set |
Most decisions are that simple. The rest of this lesson is the cases where they are not.
Two questions that settle most of it
1. Do the items have names, or only positions?
If each item means something different — a name, an age, a city — they want names, and that is a dictionary.
person = ["Priya", 28, "Pune"] # what is [1]?
person = {"name": "Priya", "age": 28, "city": "Pune"}
If they are interchangeable — twenty temperatures, a hundred filenames — a list is right.
2. Will it change?
Growing, shrinking or reordering means a list. Fixed forever means a tuple, and saying so in the type is free documentation.
Where it actually matters: speed
For small collections, anything works. The difference appears when a collection is large and you are searching it repeatedly.
import time
big_list = list(range(1_000_000))
big_set = set(big_list)
start = time.perf_counter()
999_999 in big_list
print(f"list: {time.perf_counter() - start:.6f}s")
start = time.perf_counter()
999_999 in big_set
print(f"set: {time.perf_counter() - start:.6f}s")
list: 0.012000s
set: 0.000001s
Roughly ten thousand times faster, and the gap widens as the collection grows.
The reason: in on a list compares against each item in turn. A set computes
where the value would be and looks there. Dictionaries do the same for keys.
The practical rule: searching a collection inside a loop is the situation to watch. That is two nested operations, and turning the inner one from a scan into a lookup is often the single biggest speed-up available:
# slow: scans the whole list for every word
for word in words:
if word in banned_list:
...
# fast: one conversion, then instant lookups
banned = set(banned_list)
for word in words:
if word in banned:
...
If a program that felt instant on test data crawls on real data, this is the first thing to check.
Worked examples
Storing a shopping basket. Items get added and removed, order matters, and buying two of the same thing is legitimate. → list
A student's details. Named fields. → dictionary
Many students' details. Each is a dictionary; you have several. → list of dictionaries
Looking up a student by roll number. You will ask "which student is 2314?" → dictionary keyed by roll number, not a list you search
students = {
"2314": {"name": "Priya", "city": "Pune"},
"2315": {"name": "Arjun", "city": "Delhi"},
}
print(students["2314"]["name"])
This is the switch people miss most often. A list of dictionaries is right for processing every record; a dictionary keyed by id is right for finding one. If you are writing a loop that breaks as soon as it finds a match, you probably wanted a dictionary.
An RGB colour. Always three values, always in that order. → tuple
Which tags an article has. Order irrelevant, duplicates meaningless, you ask "does it have this tag?" → set
Counting word frequency. Names (the words) mapped to values (the counts). → dictionary
Coordinates as a key. Keys must be immutable. → tuple as the key, in a dictionary
Checking a password against 10,000 banned ones. Membership, large, repeated. → set
Combining them
Real structures nest, and the right combination usually falls out of the two questions applied at each level:
orders = {
"ORD-001": {
"customer": "Priya",
"items": [
{"name": "tea", "qty": 2, "price": 40},
],
"tags": {"paid", "shipped"},
},
}
- Orders keyed by id, because you look one up → dictionary
- Each order has named fields → dictionary
- Items are a growing ordered collection → list
- Each item has named fields → dictionary
- Tags are unique, unordered flags → set
Nobody designs that in one go. It comes from asking, at each level, whether the things have names and whether the collection changes.
Changing your mind is cheap
Converting is one call:
list(my_tuple)
tuple(my_list)
set(my_list)
list(my_set)
list(my_dict) # the keys
list(my_dict.values())
list(my_dict.items()) # (key, value) tuples
So pick the structure that makes your code clearest, and convert at the point where a different one helps. Converting a list to a set for a membership check inside a hot loop is a normal, good thing to do.
Mistakes worth avoiding
A list of pairs used as a lookup table.
prices = [("tea", 40), ("coffee", 120)]
for name, price in prices:
if name == "tea":
print(price)
That is a dictionary written the long way. Use one.
A dictionary where the keys are 0, 1, 2.
items = {0: "a", 1: "b", 2: "c"}
That is a list. Use one.
A set when you needed counts. A set discards duplicates, so it has already thrown away the information you were about to ask for.
A list where nothing may repeat. If duplicates are a bug, a set makes them impossible rather than something you check for.
Practice
For each, name the structure and say why in one sentence. Then write a line or two creating it.
- The days of the week.
- A user's profile: name, email, date joined.
- Every email address that has unsubscribed, checked on each send.
- The last 50 temperature readings from a sensor.
- A chessboard square's contents, looked up by
(row, column). - How many times each error code appeared in a log file.
- All the users in a system, looked up by user id.
- All the users in a system, to be displayed in registration order.
Note that 7 and 8 describe the same users and want different structures. That is the lesson.
-
Run the timing comparison above. Then write a program that checks 10,000 words against a 50,000-word banned list, first with a list and then with a set, and time both.
-
Take this and improve the structure choice. Two things are wrong with it: the lookup scans the whole list, and
u[2]tells the reader nothing.users = [ ["priya@example.com", "Priya", True], ["arjun@example.com", "Arjun", False], ] wanted = "arjun@example.com" for u in users: if u[0] == wanted: print(u[1], u[2])Rewrite it so finding a user by email needs no loop at all.
That is module four. You can now hold real amounts of structured data, reach into it, reshape it, and choose sensibly between the options.
Next module: functions — how to stop repeating yourself, and the fix for that uncomfortable nesting in the guessing game.
Stuck on this lesson?
Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.
About the internship