Choosing a Structure
Every structure in this module answers a different question well — picking the wrong one doesn't just cost speed, it forces awkward workarounds.
By the end of this lesson, you can
- Apply a small set of questions to choose the right structure for a scenario
- Compare lists, dictionaries, sets, stacks, queues, trees, and graphs side by side
- Explain why forcing a familiar structure onto the wrong problem leads to awkward, unnecessary code
Why it matters
Every structure in this module models information differently, and picking the wrong one for a problem doesn’t just cost some speed — it usually forces genuinely awkward code, working around a mismatch that a different structure wouldn’t have had at all.
Mental model
Four questions, asked in order, point toward the right structure:
- Is order meaningful, or is the data just a collection?
- How will items be found — by position, by a meaningful key, or just “is this present at all”?
- Must duplicates be allowed, or does every value need to be unique?
- Which end (or point) does work happen at — the most recent item, the oldest, a hierarchy, or an arbitrary network of connections?
| Structure | Found by | Order | Duplicates? | Best for |
|---|---|---|---|---|
| List | Position (index) | Preserves insertion order | Allowed | An ordered collection, accessed by position |
| Dictionary | Key | Preserves insertion order, but accessed by key | Keys unique, values can repeat | Looking something up by a meaningful key |
| Set | Membership only | No meaningful order | Never — automatically collapsed | Fast uniqueness or membership checks |
| Stack | Most recently added | Last-in-first-out | Allowed | Undo history, nested structure |
| Queue | Earliest added | First-in-first-out | Allowed | Processing items in arrival order |
| Tree | Parent/child references | Hierarchical, no cycles | — | A hierarchy with one root |
| Graph | Arbitrary references | Any pattern, cycles allowed | — | A network of connections |
Small example
prices = [("milk", 3.50), ("eggs", 2.80), ("bread", 4.00)]
def find_price(prices, item):
for name, price in prices:
if name == item:
return price
return None
print(find_price(prices, "eggs"))2.8This works — but it’s a dictionary problem wearing a list’s clothes.
find_price has to check every entry one at a time, and every place
that needs a price has to remember to call this helper function instead
of writing prices["eggs"] directly.
prices = {"milk": 3.50, "eggs": 2.80, "bread": 4.00}
print(prices["eggs"])2.8Same answer, no helper function needed, and it stays fast as the number of products grows — because a dictionary is what this problem actually is.
Check your understanding
Or reveal the answer without checking
Answer:A queue
Processing in arrival order is exactly what a queue models — first in, first out — with no need for keys, hierarchy, or uniqueness.
Or reveal the answer without checking
Answer:A structure that doesn't fit the problem often still "works," at the cost of awkward workarounds and code that only gets slower and harder to follow as the data grows
A mismatched structure rarely fails outright — it just quietly costs more code, more edge cases, and worse growth behavior than the structure that actually fit the problem.
Practice: warm-up
For each scenario, name the structure that fits best: (1) checking whether an email has already registered, (2) a browser’s back-button history, (3) a family tree, (4) a road map between cities, (5) looking up a student’s grade by their ID.
Stuck? Reveal one hint at a time.
Hint 1
Ask the four questions for each: order matters? found by position, key, or membership? duplicates allowed? which end does work happen at?
Hint 2
A road map and a family tree look similar at a glance — but one has a single root and no cycles, and the other doesn't.
Reveal one reasonable set of answers
Try the problem yourself before reading this. There is often more than one reasonable approach — treat this as one worked example, not the only correct answer.
1. Already registered? -> set (pure membership check)
2. Back-button history -> stack (most recent page first)
3. Family tree -> tree (one root, hierarchy, no cycles)
4. Road map between cities -> graph (any connection pattern, cycles allowed)
5. Grade by student ID -> dictionary (lookup by a meaningful key)Practice: apply it
This code checks whether a username has already been taken, using a list and a manual search every time:
taken_usernames = ["ada99", "grace_h", "linus_t"]
def is_taken(usernames, name):
for existing in usernames:
if existing == name:
return True
return False
print(is_taken(taken_usernames, "grace_h"))
Or reveal the answer without checking
Answer:It gets slower, checking names one by one — exactly the problem a set solves, since this is a pure membership question with no order or position involved
This is a membership question through and through — no order, no position, no keys. A set answers it directly and stays fast regardless of size, while the list-based version gets slower as it grows.
Modification challenge: rewrite taken_usernames as a set and
replace is_taken with a direct name in taken_usernames check.
Summary
- Ask what’s meaningful about the data — order, lookup method, uniqueness, and which end work happens at — before choosing a structure.
- Every structure in this module answers a different combination of those questions well; none of them is a universal default.
- A structure that doesn’t fit a problem can usually still be made to work, at the cost of awkward, slower code that only gets worse as the data grows.
- When code feels like it’s fighting its own data structure — manual searches, hand-checked duplicates — that’s a sign worth reconsidering the choice, not just optimizing the workaround.