Skip to main content
Updated Feb 24, 2026

Reading SmartNotes — Your First Code Review

In Lesson 5, you learned that Python ignores type annotations at runtime and that Pyright is the tool that actually enforces them. You saw how Pyright catches five categories of type errors that Python silently allows. Now you will put every reading skill from this chapter to work on a single, real module.

Emma opens the SmartNotes project on the screen. The file is smartnotes/notes.py -- eighty lines of typed Python, generated by AI from a specification. "You have learned to read types, trace expressions, and decode function signatures," she tells James. "This is your first code review. Read every function. Predict what each one does. Explain it in plain English. If you can explain it, you understand it. If you cannot -- that is your next learning target."

James has never reviewed a full module before. Individual expressions and isolated functions were one thing. A file with multiple functions that call each other is different. Emma hands him a checklist. "Follow the same four steps for every function. Do not skip ahead. Do not run the code until you have predicted the output."


The Code Review Checklist

When reviewing any Python function, follow these four steps in order. Each step answers a different question about the code.

StepWhat You ReadQuestion It Answers
1. Signaturedef name(params) -> return:What is the contract? What goes in, what comes out?
2. TypesParameter types, return type, variable annotationsWhat kind of data flows through this function?
3. BodyThe implementation logic line by lineHow does it accomplish the contract?
4. Edge CasesEmpty inputs, None values, unexpected dataWhat happens when input is unusual?

The signature is the most important step. Before you read a single line of the body, the signature tells you the function's contract. A function that takes list[Note] and returns dict[str, int] is a counting function -- you know this before reading the implementation.


The SmartNotes Module

Here is the complete smartnotes/notes.py module. Read it top to bottom before continuing.

Loading Python environment...

The @dataclass decorator on Note is new. For now, read it as: "Python automatically creates an __init__ method that accepts title, body, and tags as arguments." You do not need to understand how @dataclass works internally. You only need to know that Note(title="...", body="...", tags=[...]) creates a Note with those three fields.


Walking Through Each Function

Apply the checklist to every function in the module.

format_title

StepWhat You Find
Signaturedef format_title(raw_title: str) -> str: -- takes a string, returns a string
TypesInput: str. Output: str. No collections, no None.
Bodyraw_title.split() breaks the string into words (splitting on any whitespace). " ".join(...) reassembles them with single spaces. .title() capitalizes the first letter of each word.
Edge casesWhat if raw_title is ""? .split() returns [], " ".join([]) returns "", .title() on "" returns "". No crash.

Plain English: "Takes a messy title with extra spaces and makes it clean and properly capitalized."

Prediction:

Loading Python environment...

Output:

Hello World

search_notes

StepWhat You Find
Signaturedef search_notes(notes: list[Note], query: str) -> list[Note] -- takes a list of notes and a search term, returns matching notes
TypesInput: list[Note] and str. Output: list[Note].
BodyConverts query to lowercase. Loops through each note. Checks if the lowercase query appears in the lowercase title OR the lowercase body. If so, appends the note to results.
Edge casesWhat if no notes match? Returns an empty list [] -- not None. What if query is ""? Every note matches because "" in "any string" is True.

Plain English: "Searches through all your notes and returns the ones that mention a specific word in the title or body."

count_by_tag

StepWhat You Find
Signaturedef count_by_tag(notes: list[Note]) -> dict[str, int] -- takes notes, returns a tag-to-count mapping
TypesInput: list[Note]. Output: dict[str, int]. The return type tells you this is a counting function before you read the body.
BodyCreates empty dictionary. For each note, for each tag in that note, increments the count (or starts at 1 if the tag has not been seen).
Edge casesWhat if a note has an empty tags list? The inner loop runs zero times -- no crash, no entry added.

Plain English: "Counts how many notes are filed under each tag, like counting how many sticky notes are in each folder."

create_sample_notes

StepWhat You Find
Signaturedef create_sample_notes() -> list[Note] -- takes nothing, returns a list of notes
TypesNo input. Output: list[Note]. A factory function.
BodyReturns a hardcoded list of four Note objects with titles, bodies, and tags.
Edge casesNone -- deterministic, no input to vary.

Plain English: "Creates four sample notes so you have data to work with."

main

StepWhat You Find
Signaturedef main() -> None -- takes nothing, returns nothing. A side-effect function.
Types-> None means this function DOES things (prints) but does not RETURN a value.
BodyCreates sample notes. Searches for "python". Prints how many matched and their titles. Counts tags. Prints the tag counts.
Edge casesNone -- calls functions with known inputs.

Plain English: "Runs the whole program: makes sample notes, searches them, and prints the results."


Exercise 1: Predict the Output of main()

Before running the code, trace through main() and write down exactly what it will print. Use what you know about search_notes and count_by_tag.

Step 1: create_sample_notes() returns four notes. Their titles are "Python Types", "Trace Tables", "PRIMM Method", "Pyright Errors".

Step 2: search_notes(notes, "python") searches for "python" (case-insensitive). Check each note:

Note TitleTitle contains "python"?Body contains "python"?Match?
Python TypesYes ("python" in "python types")NoYes
Trace TablesNoNoNo
PRIMM MethodNoNoNo
Pyright ErrorsNoNoNo

Wait -- check carefully. The body of "Python Types" is "Every variable has a type annotation." Does that contain "python"? No. But the title "Python Types" lowercased is "python types", and "python" is in "python types". So it matches.

What about "Pyright Errors"? The title lowercased is "pyright errors". Does "python" appear in "pyright errors"? No -- "pyright" is not "python". It does not match.

Result: Only 1 note matches.

Step 3: count_by_tag(notes) counts all tags across all four notes:

TagCount
"python"3 (Python Types, Trace Tables, Pyright Errors)
"types"2 (Python Types, Pyright Errors)
"debugging"1 (Trace Tables)
"pedagogy"1 (PRIMM Method)

Predicted output:

Found 1 notes matching 'python':
- Python Types

Tag counts: {'python': 3, 'types': 2, 'debugging': 1, 'pedagogy': 1}

Run the verification:

uv run python -m smartnotes.notes

Compare your prediction to the actual output. If they match, you traced correctly. If they differ, find where your trace went wrong -- that is your learning edge.


Exercise 2: Explain in Plain English

For each function below, write one sentence describing what it does. Use words a non-programmer would understand. No code jargon allowed.

search_notes: Write your sentence here. Compare to this benchmark: "Looks through all your notes and picks out the ones that mention a specific word anywhere in the title or text."

count_by_tag: Write your sentence here. Compare to this benchmark: "Goes through every note, looks at its labels, and tallies up how many notes share each label."

The test is simple: could someone who has never programmed understand your sentence? If yes, you understand the function. If you find yourself reaching for words like "iterates" or "returns a dictionary" -- you are describing syntax, not meaning.


Exercise 3: Parsons Problem

The lines of format_title are scrambled below. Reorder them so the function works correctly. There is only one valid order.

A:  return " ".join(raw_title.split()).title()
B: def format_title(raw_title: str) -> str:
C: """Remove extra whitespace and capitalize each word."""

The correct order is B, C, A. The function definition comes first (def line), then the docstring (immediately after def), then the return statement. In Python, indentation determines what belongs inside a function -- both C and A are indented under B.


Exercise 4: Full Code Review -- What Does main() Print?

This exercise repeats the prediction from Exercise 1, but now do it without the guided trace table. Look at the main() function, trace through each call mentally, and write the exact output. Check your answer against the prediction in Exercise 1.

If you got it right without the guide, the checklist has become intuition. If you needed to refer back, that is normal -- keep practicing.


Running the Verification Pipeline

This is the Chapter 16 checkpoint. Copy the SmartNotes module into your project and run the full verification pipeline:

uv run pyright

Output (expected):

0 errors, 0 warnings, 0 informations

Every type annotation in the module is correct. Pyright confirms that search_notes returns list[Note] (not None), that count_by_tag returns dict[str, int], and that main calls each function with the right argument types. The module passes the type checker with zero errors.

If you want to run the full pipeline from Chapter 15:

uv run ruff check . && uv run pyright && uv run pytest

All three tools should pass. The discipline stack you built in Chapter 15 is now verifying real code.


Chapter 16 Syntax Card

Every piece of new syntax from this chapter, in one place. Keep this as a reference.

Loading Python environment...


Try With AI

Open your AI coding assistant and try these prompts to extend your code review skills.

Prompt 1: Generate a New Function for Review

Generate a new SmartNotes function called filter_by_tag that takes
a list of Note objects and a tag string, and returns only the notes
that have that tag. Use full type annotations. Include a docstring.
Do NOT explain the code -- just give me the function. I want to
review it myself using my checklist.

What you're learning: You are practicing the code review checklist on code you have not seen before. The AI generates a function; your job is to apply the four steps (signature, types, body, edge cases) and explain what it does in plain English. This is the core skill of the AI era -- verifying code that someone else (or something else) wrote.

Prompt 2: Explore a Type Error

What would Pyright report if search_notes returned None instead of
an empty list when no notes match? Show me the exact error message
Pyright would produce and explain why returning None when the return
type says list[Note] is a problem.

What you're learning: You are deepening your understanding of why return types matter. When search_notes promises list[Note], every caller can safely write for note in results: without checking for None. If the function secretly returns None, that loop crashes. Pyright catches this at edit time so the crash never happens at runtime.

Prompt 3: Understand the Dataclass Decorator

Explain the @dataclass decorator to me as if I am a beginner who
has never seen decorators. What does it do to the Note class?
What code does Python generate automatically? Show me what Note
would look like WITHOUT @dataclass so I can see what it replaces.

What you're learning: You are building a mental model of what @dataclass does by seeing the code it replaces. The AI will show you that without @dataclass, you would need to write an __init__ method, a __repr__ method, and an __eq__ method manually. This preview prepares you for Chapter 17 where you will encounter decorators and classes in more depth.


Key Takeaways

  1. The code review checklist has four steps: signature, types, body, edge cases. Always read the signature first. It tells you the function's contract -- what it accepts and what it promises to return -- before you read a single line of implementation.

  2. Plain English explanation tests real understanding. If you can describe what a function does in one sentence that a non-programmer would understand, you comprehend the code. If you reach for jargon like "iterates" or "returns a dictionary," you are describing syntax, not meaning.

  3. Tracing through multiple function calls is the capstone reading skill. Reading main() requires understanding every function it calls. You traced search_notes on four notes, predicted one match, and verified the tag counts. That multi-function trace is what code review looks like in practice.

  4. Edge cases reveal robustness. What does search_notes return when nothing matches? An empty list, not None. What does format_title do with an empty string? Returns an empty string, no crash. These details matter because they determine whether the code handles unexpected input gracefully.

  5. The syntax card is your chapter reference. Every type annotation, conversion, string method, and operator from Chapter 16 is collected in one place. Use it as a quick lookup when you encounter unfamiliar syntax in future chapters.


Looking Ahead

You can read Python. You traced types, predicted output, decoded function signatures, and reviewed a complete SmartNotes module using a structured checklist. In Chapter 17, you will cross from reading to writing -- but not the way you might expect. Your first lines of code will be a test: five lines that define what "correct" means. Then you will watch AI implement the function that passes your test. That is Test-Driven Generation -- the bridge from reading code to owning it.