Skip to main content

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."

If you're new to programming

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:

ProblemWhat happensWhen you discover it
1. Typos crash at runtimenote["titel"] raises KeyErrorOnly when that line runs (maybe in production)
2. No tooling supportPyright reports 0 warnings; editor offers no autocompleteNever, unless you read every key by hand
3. Silent corruptionWrong keys are added without any errorWhen 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

Opening Claude Code

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

  1. 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.

  2. Pyright cannot help with dictionary key access. It reports zero warnings for misspelled keys because the type signature allows all strings.

  3. Editor autocomplete is useless for dict keys. Without a defined structure, your editor cannot suggest valid keys, and you type from memory.

  4. 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.

  5. 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.