Lesson 10: Choosing the Right Structure
Opening: The Architect's Moment
You've now learned three powerful data structures: lists (flexible, mutable sequences), tuples (immutable, hashable sequences), and dictionaries (fast key-value lookups). But here's the real challenge that separates good developers from great ones: knowing which structure to use and why.
This isn't about syntax anymore. You've mastered that. This is about architectural thinking—understanding that your data structure choice communicates intent, enables efficient algorithms, and prevents bugs. A professional developer doesn't just ask "Can I do this?" They ask "What's the right structure for this job?"
This lesson synthesizes everything you've learned across Lessons 1-9. You won't learn new concepts, but you'll learn how to think about data structures like an architect.
Decision Framework: The Four Questions
When faced with a problem that requires storing and organizing data, you're really answering four questions:
Question 1: Do I Need to Change Data?
The Mutability Question
- List: Yes, I'll modify this data (add, remove, sort, update items)
- Tuple: No, this data should be fixed and unchangeable
- Dict: Yes, I'll modify values (but keys stay stable)
Example:
# Mutable: Shopping cart gets updated
shopping_cart: list[str] = ["milk", "eggs", "bread"]
shopping_cart.append("cheese") # ✓ This works
# Immutable: Coordinates are fixed
location: tuple[float, float] = (40.7128, -74.0060) # NYC
location[0] = 40.7130 # ✗ TypeError: tuples don't support item assignment
💬 AI Colearning Prompt
"Why would I choose immutability for data that never changes? What problems does immutability prevent?"
This question alone eliminates half your options. If you need to change data, reach for a list or dict. If not, tuples offer benefits (hashable, memory-efficient, intent-signaling).
Question 2: Does Order Matter?
The Ordering Question
- List: Order matters (I access by position, or order conveys meaning)
- Tuple: Order matters but I don't change it (positional data, multiple return values)
- Dict: Order doesn't matter (I access by meaningful key, not position)
Example:
# Order matters: To-do list (first task is highest priority)
tasks: list[str] = ["Finish report", "Email client", "Prepare meeting"]
print(tasks[0]) # "Finish report" (position matters)
# Order doesn't matter: Phone directory
contacts: dict[str, str] = {
"Alice": "555-1234",
"Bob": "555-5678",
"Charlie": "555-9012"
}
# I look up by name, not by position
alice_phone = contacts["Alice"] # ✓
🎓 Instructor Commentary
In AI-native development, you're not memorizing how Python sorts dicts (insertion order in 3.7+). You're understanding the intent: lists when order is semantically meaningful, dicts when you need named lookup. Structure choice is communication.
Question 3: How Do I Find What I Need?
The Lookup Pattern Question
- List: By position (index 0, 1, 2...) or by searching (slow, O(n))
- Tuple: By position (same as list)
- Dict: By key (fast, O(1) guaranteed)
This is where performance awareness enters. If you have 10,000 items and need to find one frequently, a dict with O(1) lookup is orders of magnitude faster than a list with O(n) search.
Example: The Performance Difference
# List: Finding a value requires searching
student_ids: list[int] = [1001, 1002, 1003, 1004, 1005, 1006, 1007, 1008]
target: int = 1007
# Slow approach: Search through every item until found
found: bool = target in student_ids # O(n) - checks 1001, 1002, ..., 1007
# Dict: Direct lookup by key
student_info: dict[int, str] = {
1001: "Alice",
1002: "Bob",
1003: "Charlie",
1007: "David"
}
# Fast approach: Direct key lookup
student_name: str = student_info[1007] # O(1) - instant, no searching
🚀 CoLearning Challenge
Ask your AI Co-Teacher:
"Explain the difference between O(1) and O(n) lookup. Show me a concrete example with a list of 1,000,000 items where a dict is dramatically faster."
Expected Outcome: You'll understand why lookup pattern determines structure choice for large datasets.
✨ Teaching Tip
When performance questions arise, use Claude Code to test: "Create a list with 100,000 names. Time how long it takes to find 'Zebra' using
inoperator. Then do the same with a dict. Show me the time difference."
Question 4: What Does This Structure Say About My Intent?
The Semantics Question
Data structures communicate. When you write list[str], you're saying "order and mutability matter." When you write dict[str, int], you're saying "I'm mapping meaningful keys to values." When you write tuple[int, int], you're saying "this pair is atomic, unchangeable."
Examples of Semantic Communication:
# "This is an ordered sequence I'll modify"
playlist: list[str] = ["Song A", "Song B", "Song C"]
# "This is a fixed pair (coordinates can't change)"
coordinates: tuple[int, int] = (100, 200)
# "This maps user IDs to their profile data"
user_profiles: dict[int, dict[str, str]] = {
101: {"name": "Alice", "email": "[email protected]"},
102: {"name": "Bob", "email": "[email protected]"}
}
When other developers (or future you) read your code, the structure choice tells them your intent without additional comments.
Decision Matrix: Quick Reference
Here's a framework to help you decide:
| Scenario | Use | Why |
|---|---|---|
| Ordered list that changes (add/remove/sort) | list[T] | Mutable, ordered, flexible |
| Fixed collection of values (coordinates, RGB color) | tuple[T, ...] | Immutable, hashable, semantic clarity |
| Use as dict key | tuple[T, ...] | Only hashable types work as keys |
| Fast lookup by name/ID | dict[K, V] | O(1) lookup vs O(n) list search |
| Config settings (keys are meaningful) | dict[str, Any] | Natural mapping, self-documenting |
| Iterate and modify | list[T] | Natural iteration with index access |
Real-World Scenarios: The Decisions Developers Make
Scenario 1: Student Database
Problem: Store student records. You need to:
- Look up students by ID (frequently)
- Store multiple fields per student (name, email, major, GPA)
- Add/remove students (semester changes)
Decision:
# ✓ CORRECT: Dict of student records
students: dict[int, dict[str, str | int]] = {
1001: {"name": "Alice", "major": "CS", "gpa": 3.8},
1002: {"name": "Bob", "major": "Math", "gpa": 3.2},
}
# Lookup is fast
student = students[1001] # O(1)
# Easy to add new students
students[1003] = {"name": "Carol", "major": "Physics", "gpa": 3.5}
Why not a list?
# ✗ LESS IDEAL: List of student records
students_list: list[dict[str, str | int]] = [
{"id": 1001, "name": "Alice", "major": "CS", "gpa": 3.8},
{"id": 1002, "name": "Bob", "major": "Math", "gpa": 3.2},
]
# Lookup requires searching
def find_student(sid: int):
for student in students_list: # O(n) - must check every student
if student["id"] == sid:
return student
The dict is the right structure because lookup speed matters and IDs are meaningful keys.
Scenario 2: Shopping Cart
Problem: Store a customer's shopping cart. You need to:
- Keep items in order (display order on website)
- Add/remove items
- Possibly sort by price
Decision:
# ✓ CORRECT: List of items
shopping_cart: list[str] = ["Laptop", "Mouse", "USB Cable", "Monitor"]
# Easy to modify
shopping_cart.append("Keyboard")
shopping_cart.remove("USB Cable")
# Easy to sort
shopping_cart.sort() # Alphabetical order
# Order is preserved and meaningful
print(shopping_cart[0]) # First item in cart
Why not a dict?
# ✗ LESS IDEAL: Dict of items
cart_dict: dict[str, bool] = {
"Laptop": True,
"Mouse": True,
"USB Cable": True,
"Monitor": True
}
# Problems:
# - Lost original order (website needs to show items in customer's order)
# - Sorting becomes complicated
# - No natural way to handle duplicates (multiple Laptops)
The list is the right structure because order is meaningful and modifications are common.
Scenario 3: Application Configuration
Problem: Store app settings (theme, language, timeout values). You need to:
- Access settings by meaningful name (not index)
- Store different value types (strings, integers, booleans)
- Prevent accidental modification (sometimes)
Decision:
# ✓ CORRECT: Dict of settings
config: dict[str, str | int | bool] = {
"theme": "dark",
"language": "en",
"timeout_seconds": 30,
"debug_mode": False
}
# Meaningful access
current_theme: str = config["theme"]
# Easy to add settings
config["max_retries"] = 3
# Self-documenting (keys are meaningful)
Why not a list?
# ✗ LESS IDEAL: List of settings
config_list: list[str | int | bool] = [
"dark", # theme at position 0 - what does this mean?
"en", # language at position 1 - why this order?
30, # timeout at position 2 - who remembers this?
]
# Accessing requires remembering positions
current_theme = config_list[0] # Unclear what this is
# Prone to index errors
# config_list[4] might not exist or might be the wrong setting
The dict is the right structure because keys communicate intent and random access doesn't require remembering positions.
Anti-Patterns: What NOT to Do
Anti-Pattern 1: Using List When Dict Makes Sense
# ✗ BAD: List with manual searching
users: list[dict[str, str]] = [
{"id": "user1", "name": "Alice"},
{"id": "user2", "name": "Bob"},
]
def find_user(user_id: str) -> dict[str, str] | None:
for user in users: # O(n) lookup
if user["id"] == user_id:
return user
return None
Problem: Every lookup is slow. With 10,000 users, you search 5,000 on average.
Solution:
# ✓ GOOD: Dict with O(1) lookup
users: dict[str, str] = {
"user1": "Alice",
"user2": "Bob",
}
user_name = users.get("user1", "Unknown") # O(1) - instant
💬 AI Colearning Prompt
"I have a list of 100,000 products and frequently search by product ID. Why would switching to a dict improve my application's speed? Show me the difference."
Anti-Pattern 2: Using Dict When List Is Natural
# ✗ BAD: Dict when list is more natural
tasks: dict[int, str] = {
0: "First task",
1: "Second task",
2: "Third task",
}
# Problems:
# - Why use integers as keys if list exists?
# - Harder to iterate in order (relies on insertion order in Python 3.7+)
# - Harder to extend/rearrange
Problem: You're using dict features you don't need, making code less clear.
Solution:
# ✓ GOOD: List when order and position matter
tasks: list[str] = [
"First task",
"Second task",
"Third task",
]
# Natural iteration and ordering
for i, task in enumerate(tasks):
print(f"Task {i}: {task}")
Anti-Pattern 3: Mixing Tuple and List Confusingly
# ✗ BAD: Unclear when to use tuple vs list
coordinates: list[int] = [10, 20] # Looks like it could be modified
point_a = coordinates
coordinates.append(30) # Now point_a changed too!
# Safer approach:
point_b: tuple[int, int] = (10, 20) # Clear: immutable
Problem: Using list for immutable data creates aliasing bugs.
Solution: Use tuples when data is conceptually fixed, even if Python doesn't enforce it.
Performance Implications: Understanding the Tradeoffs
This is conceptual understanding, not implementation details. You don't need to know how Python implements hash tables—you need to understand the implications:
| Operation | List | Dict |
|---|---|---|
| Add item | O(1) amortized | O(1) amortized |
| Remove item | O(n) (may shift items) | O(1) |
| Find by position | O(1) | O(n) (must iterate) |
| Find by value/key | O(n) | O(1) |
| Memory overhead | Minimal | Higher (hash table) |
The Trade-off: Dicts use more memory but give you O(1) lookup. Lists use less memory but require O(n) search. Choose based on your access patterns.
# If you need FAST LOOKUP: Dict is worth the memory
user_count: int = 1_000_000
user_lookup: dict[int, str] = {} # Higher memory, but instant lookups
# If you rarely lookup and memory matters: List is sufficient
user_count: int = 10
user_list: list[str] = [] # Lower memory, lookups are fine (small dataset)
✨ Teaching Tip
Ask your AI: "Design a solution for storing 1 million employee records. Compare a list-based search vs dict-based lookup. Show me timing differences and discuss when each is appropriate."
Type Hints as Intent Communication
Your type hints tell the story of your choice:
# "I have an ordered sequence I'll modify"
playlist: list[str]
# "I have a fixed pair of values"
location: tuple[float, float]
# "I have a mapping from IDs to names"
employees: dict[int, str]
# "I have a list of dictionaries (records)"
students: list[dict[str, str | int]]
# "I have a dictionary with nested structure"
user_data: dict[str, dict[str, str | int]]
Other developers (and future you) understand your intent by reading the type hints. This is part of your architectural thinking.
Exercise 1: Decision Analysis
For each scenario, identify the best data structure and explain why:
Scenario A: Store a student's test scores. You need to track which test each score is from and calculate average.
Scenario B: Store the colors of a traffic light in order (red, yellow, green). Colors never change.
Scenario C: Store a phone book (name → phone number). Need to look up numbers by name frequently.
Scenario D: Store a list of items to buy. You'll add/remove items and sometimes reorder them.
Your Task: Write down your choice for each scenario and one sentence explaining why. Then, use Claude Code to verify your reasoning:
"I chose [structure] for [scenario] because [reason]. Does this make sense?"
Exercise 2: Anti-Pattern Recognition
Here's code with a structural choice problem. Identify it and explain how you'd refactor:
# Current code
data: list[dict[str, str]] = [
{"isbn": "978-0134494166", "title": "Clean Code"},
{"isbn": "978-0201633610", "title": "Design Patterns"},
{"isbn": "978-0134685991", "title": "Clean Architecture"},
]
def find_book(isbn: str) -> dict[str, str] | None:
for book in data:
if book["isbn"] == isbn:
return book
return None
# Every lookup requires searching all books
found = find_book("978-0134494166")
Your Task: Identify the anti-pattern, explain the performance problem, and refactor using the right structure.
Exercise 3: Real-World Project Design
Design the data structures for a simple music playlist application:
- Users have playlists (multiple per user)
- Each playlist has songs in order
- Users can mark favorite songs (immutable list)
- You need to find songs by artist name quickly
Your Task:
- Write the data structures using proper type hints
- Explain each choice (mutability, ordering, lookup pattern)
- Show example code creating a user with playlists
Try With AI
Choose your preferred AI companion (ChatGPT web, Claude Code, or Gemini CLI).
Prompt 1: Recall Decision Criteria (Remember)
"List the four key questions I should ask when choosing between list, tuple, and dict. For each question, explain what each structure provides."
Expected Outcome: You'll recall the decision framework and reinforce when to use each structure based on mutability, ordering, and lookup patterns.
Prompt 2: Analyze a Real Scenario (Understand)
"I'm building a social media app. I need to store:
- User posts (need to display newest first)
- User's followers (need to check if person X follows them)
- Post metadata (likes, timestamps, author ID)
Which data structures would you use and why? Explain the tradeoffs."
Expected Outcome: You'll understand how different requirements drive different structural choices and can articulate tradeoffs between performance and simplicity.
Prompt 3: Evaluate Architectural Decisions (Apply)
"Here's a design: I'm using a list of dicts for user records and searching by user ID every time. Explain why this might be a problem for a site with 1 million users. What structure would be better and why?"
Expected Outcome: You'll connect structure choice to real-world performance implications and can argue for refactoring in code reviews.
Prompt 4: Justify a Design Decision (Analyze/Evaluate)
"Imagine you're in a code review and see this:
student_records: list[tuple[int, str, str, float]] = [
(1001, 'Alice', 'CS', 3.8),
(1002, 'Bob', 'Math', 3.2),
]The developer says 'I used tuples because the data never changes.' Is this the best choice? What would you suggest and why? Show me the refactored version."
Expected Outcome: You'll develop professional judgment about architectural decisions and can communicate recommendations constructively. You'll understand when semantic clarity (using dict) trumps technical correctness (using tuple).
Safety & Ethics Note: When AI suggests structures, verify they match your requirements. Sometimes multiple structures could work—your job is understanding the tradeoffs. Always ask "Why this over that?" to deepen your architectural thinking.