Why Dicts Break: The Pain Chapter
James is finishing a SmartNotes function that stores notes as dictionaries. He types note["titel"] and presses Enter. Python crashes with a KeyError. He stares at the screen, scrolls up, and sees the key is spelled "title". One missing letter, and the whole program stops.
"That is the third time this week," he says, rubbing his eyes.
Emma pulls up his test file. "Did pyright warn you about the typo?"
James checks the terminal. No warnings. No red squiggles in the editor. Pyright has nothing to say about dictionary key access. As far as the type checker knows, note["titel"] is perfectly valid code.
"So I have no safety net," James says.
"None," Emma confirms. "And that is exactly the problem we are going to solve this chapter. But first, you need to feel the full weight of it."
A dictionary (dict) stores key-value pairs, like a real dictionary maps words to definitions. You have been using them since Phase 2. This lesson does not teach anything new. Instead, it asks you to notice what goes wrong when you rely on dicts for structured data. The frustration you feel here is the motivation for everything that follows.
The Typo That Pyright Cannot Catch
Create a file called dict_pain.py with this function:
def create_note() -> dict[str, str]:
"""Create a simple note as a dictionary."""
note: dict[str, str] = {
"title": "Meeting Notes",
"body": "Discussed project timeline and deliverables.",
"author": "James",
}
return note
Now write a function that reads from the note:
def print_note_header(note: dict[str, str]) -> str:
"""Format a note header for display."""
return f"Note: {note['titel']} by {note['author']}"
Do you see the bug? The key "titel" is misspelled. Run uv run python dict_pain.py with a quick test at the bottom:
my_note: dict[str, str] = create_note()
print(print_note_header(my_note))
What happens:
KeyError: 'titel'
Python crashes at runtime. The program was running fine until it hit that one line.
Now run pyright on the same file:
uv run pyright dict_pain.py
What pyright reports:
0 errors, 0 warnings, 0 informations
Zero warnings. Pyright knows the variable is a dict[str, str], but it cannot know which specific keys exist. Any string key is valid as far as the type system is concerned. The typo is invisible to every tool except the Python runtime itself.
No Autocomplete Either
Open dict_pain.py in your editor. Place your cursor after note[ and wait for autocomplete suggestions.
Nothing useful appears. Your editor cannot suggest "title", "body", or "author" because dict[str, str] says nothing about which keys the dictionary contains. Every string is a valid key. You are typing from memory, and memory is unreliable.
Compare this to what happens when you type a variable name and press dot (.). For a string variable, your editor immediately shows .upper(), .lower(), .split(), and dozens of other methods. That is because str is a defined type with known attributes. Dictionaries with string keys have no such structure.
Silent Wrong Data
There is a subtler problem. Dicts do not prevent you from adding unexpected keys:
def add_metadata(note: dict[str, str]) -> dict[str, str]:
"""Add metadata fields to a note."""
note["word_count"] = str(len(note["body"].split()))
note["staus"] = "draft" # Typo: should be "status"
return note
This function runs without error. It creates a key called "staus" instead of "status". Later, when another function looks for note["status"], it crashes. The bug was introduced silently in one function and explodes in a completely different one. Tracing it back to the source requires reading every function that touches the dictionary.
The Three Problems
After working through these examples, you should feel three distinct frustrations:
| Problem | What happens | When you discover it |
|---|---|---|
| 1. Typos crash at runtime | note["titel"] raises KeyError | Only when that line runs (maybe in production) |
| 2. No tooling support | Pyright reports 0 warnings; editor offers no autocomplete | Never, unless you read every key by hand |
| 3. Silent corruption | Wrong keys are added without any error | When a different function fails later |
All three problems share a root cause: dict[str, str] tells Python "this maps strings to strings" but says nothing about which strings are valid keys. The structure lives in your head, not in the code.
PRIMM-AI+ Practice: Dict Bug Hunt
Predict [AI-FREE]
Read this code without running it. Write down what you think will happen, along with a confidence score from 1 to 5.
def get_note_summary(note: dict[str, str]) -> str:
"""Return a one-line summary of the note."""
title: str = note["title"]
word_count: str = note["word_count"]
return f"{title} ({word_count} words)"
test_note: dict[str, str] = {
"title": "Weekly Review",
"body": "This week we shipped three features.",
"author": "Emma",
}
print(get_note_summary(test_note))
Check your prediction
The function crashes with KeyError: 'word_count'. The dictionary test_note has three keys: "title", "body", and "author". It does not have a "word_count" key. The function assumes a key exists that was never created.
Pyright would report 0 errors for this code. The mismatch between what the function expects and what the dictionary contains is completely invisible to the type checker.
Run
Create the file and run it. Compare the crash to your prediction.
Investigate
Run uv run pyright on the file. Confirm that it reports zero errors. Then ask yourself: if pyright cannot catch this, and the editor cannot autocomplete the keys, what is your only safety net?
The answer is: tests that happen to exercise that code path. If no test calls get_note_summary with a dictionary missing "word_count", the bug survives until production.
Modify
Fix the code so it works: add "word_count" to the dictionary. Then introduce a new typo on purpose (misspell any key). Run the file and observe the crash. Run pyright and observe silence.
Make [Mastery Gate]
Without looking at any examples, list three specific problems with using dict[str, str] for structured data. For each problem, write one sentence describing it and one sentence explaining when you would discover the bug. Use your own words and draw from what you experienced in this lesson.
Try With AI
If Claude Code is not already running, open your terminal, navigate to your SmartNotes project folder, and type claude. If you need a refresher, Chapter 44 covers the setup.
Prompt 1: Diagnose the Problem
Here is a Python function that uses dict[str, str] to store
notes. What problems could arise from this approach?
def create_note(title: str, body: str) -> dict[str, str]:
return {"title": title, "body": body, "status": "draft"}
Read the AI's response. Does it mention the same three problems you identified? Did it mention any problems you missed?
What you're learning: You are comparing your own analysis to the AI's, strengthening your ability to identify structural weaknesses in code.
Prompt 2: Generate a Bug
Write a Python function that takes a dict[str, str] note and
has a subtle key-name typo that would crash at runtime but
pass pyright with zero warnings. Show what the crash looks
like.
Review the AI's output. Can you spot the typo before reading the explanation? This tests whether you have internalized the pattern from this lesson.
What you're learning: You are practicing bug detection on code you did not write, which is the core skill of code review.
Key Takeaways
-
dict[str, str]says nothing about which keys are valid. Any string key is acceptable to the type checker, so typos and missing keys are invisible until runtime. -
Pyright cannot help with dictionary key access. It reports zero warnings for misspelled keys because the type signature allows all strings.
-
Editor autocomplete is useless for dict keys. Without a defined structure, your editor cannot suggest valid keys, and you type from memory.
-
Silent corruption is the worst failure mode. A wrong key is added in one function and causes a crash in a different function, making the bug hard to trace.
-
These three problems motivate structured data models. The rest of this chapter introduces tools that make invalid keys impossible, give you full autocomplete, and let pyright catch mistakes before you run the code.
Looking Ahead
You now feel the pain of unstructured dictionaries. In Lesson 2, you will learn what the @ symbol means in Python. That small piece of syntax is the key to unlocking a better way to define data structures.