P.8 · Reading Code

P.8 · 45 min

Reading Code

Writing code was the skill that used to be mandatory. Agents changed the mandatory one to reading, because every line an agent produces is a line somebody must be able to read, and the somebody is you. Here is the good news buried in that: reading is far more learnable than writing, it compounds faster, and there is a method. Nobody reads code top to bottom like a novel. Skilled readers interrogate it, in a specific order, and this lesson is that order.

The map: how to approach a file you have never seen

Move 1: perimeter first. Before reading any logic, answer three questions from the file's edges. What comes in? (Look at the imports.) What goes out? (Look at the exports.) What is it named? Names are the author's one-sentence summaries; a file called report.ts exporting buildReport has told you the plot before you read a line of logic.

Move 2: find the entry point and follow the data. Every file has a gravitational center: the exported function everything else serves. Start there, and trace one concrete input through it. Not "what does this function do in general" but "if I call this with the string '2 days', which branch runs, what does each line do to my value, what comes out." Concrete values turn abstract code into arithmetic you can check.

Move 3: helpers on demand. When the entry point calls something you do not know, jump to it, get what you need, jump back. Reading is a stack, not a scroll: you push down into a call, answer your question, pop back up. Three levels of that is enough to understand most behavior in most codebases.

Move 4: the deletion test. For any line or block you think you understand, ask: what would break if I deleted this? If you can name the input that would now misbehave, you understand the line. If you cannot, you have a feeling, not comprehension. This question is the single best comprehension check in code review, and it works because code earns its existence by handling cases; find the case, and the code becomes obvious.

Reading a diff uses the same moves at smaller scale. A diff arrives in hunks (blocks of change with a few lines of context). For each hunk: what was true before, what is true after, and which inputs behave differently now? A diff is a claim about improvement; hunk-by-hunk reading is how you audit the claim. This becomes your daily bread in Part 3 when agents send you diffs all day.

The codebase you will read

The lab uses vercel/ms, a tiny real library used by millions of projects to convert time strings like "2 days" into milliseconds and back. It was chosen deliberately: one source file, real TypeScript, actively maintained, and its call graph is exactly three levels deep. Everything lives in src/index.ts.

Your reconnaissance, before the lab, applying move 1 from here:

  • The file imports nothing. Everything it needs, it defines: constants like const d = h * 24 (milliseconds per day).
  • It exports four things: ms (the star), parse, parseStrict, and format.
  • ms is a dispatcher: string input goes to parse, number input goes to format, anything else throws.
  • format picks between two private helpers: fmtLong (with options.long, producing "2 days") and fmtShort (default, producing "2d").
  • fmtLong calls one more helper, plural, which decides between "1 day" and "2 days".

So the deepest chain is ms(60000, { long: true })formatfmtLongplural. Three levels. You are about to walk it.

Tutor first

PromptCode reading coach

You are my code-reading coach. I am about to read src/index.ts of the vercel/ms library. Do not summarize it for me and do not explain any function before I attempt it. Instead: give me one concrete input at a time (like ms("2 days") or ms(-3000)) and ask me to trace it through the file and predict the output, step by step. Check my trace against the real code and challenge any step I hand-wave. Then ask me one deletion question: point at a specific line and ask what would break without it. After 6 rounds, tell me which reading move I am weakest at.

The lab: guided read of vercel/ms

Lablab-p-8Read a real library

Goal: Answer concrete comprehension questions about vercel/ms from the source alone, including a three-level trace and two deletion tests.

  1. Get the code and open the file:
cd && git clone https://github.com/vercel/ms.git
cd ms
cat src/index.ts

(Any editor works too. If cat fills the terminal, less src/index.ts pages it; q quits.)

  1. Perimeter. Write down, from the file only: the four exported functions, and what type each accepts and returns (the signatures say so). Note the constants block at the top and what y = d * 365.25 tells you about how this library thinks about a "year."

  2. Trace 1, two levels. Predict ms("2 days"), then verify your prediction by reading: ms sees a string, hands it to parse; parse runs a regex that captures value 2 and unit days, the switch multiplies by the day constant. Now check yourself against reality:

node -e "const {ms} = require('./src/index.ts'); console.log(ms('2 days'))"

If that errors on your setup (TypeScript loading varies by Node version), verify the old way: 2 times 24 times 60 times 60 times 1000. Either way you should land on 172800000.

  1. Trace 2, three levels. Predict ms(60000, { long: true }) before reading. Then follow it: ms sees a number, calls format; format sees options.long is true, calls fmtLong(60000); fmtLong walks its thresholds top down (years? months? weeks? days? hours? no...) until msAbs >= m matches, and calls plural(60000, 60000, 60000ms-per-minute, "minute"). Inside plural: is 60000 at least 1.5 minutes? No. So no s is appended. Answer: "1 minute". If your prediction differed, find the exact line where your model of the code diverged from the code; that line is today's lesson.

  2. Deletion test 1. In parse, the destructuring line reads const { value, unit = 'ms' } = match.groups. What breaks if the = 'ms' default is deleted? Form your answer, then check it: which inputs have no unit? Bare numbers like ms("100"). Without the default, the switch receives undefined and falls to its failure path, and plain numeric strings stop working. One tiny default carries an entire documented feature.

  3. Deletion test 2. Both fmtShort and fmtLong start with const msAbs = Math.abs(ms) and compare thresholds against msAbs, not ms. What breaks if you compared ms directly? Answer: every negative duration. -172800000 is less than every positive threshold, so -2 days would format as milliseconds instead of days. The author spent a line to make negatives work; the file never says so in a comment. The code is the only documentation of half of any codebase's decisions.

  4. Diff reading, one rep. Look at the project's history for a small change:

git log --oneline -15
git show --stat HEAD
git show HEAD

Pick any commit whose diff touches src or tests. For each hunk, write one sentence: before, after, which inputs changed behavior. If the diff is pure housekeeping (version bumps, config), say that in a sentence instead; recognizing a no-behavior-change diff quickly is itself a review skill.

Verify

  • Your traces produced 172800000 and "1 minute", and where they did not, you found the divergent line.
  • You can answer both deletion tests without rereading this lesson.
  • You wrote a one-sentence-per-hunk summary of one real commit.
>Troubleshooting
  • git clone fails. You need git configured (P.3) and network access. Behind a corporate proxy, GitHub over HTTPS is occasionally blocked; the file is also readable in the browser at the source URL in this lesson's frontmatter, and every exercise works from the browser view.
  • The node -e verification errors with something about TypeScript or modules. Expected on some setups; running TS directly depends on your Node version and flags. The lab's point is the reading, so verify traces by arithmetic and by the unit tests instead: open the test file and find the assertions for your input.
  • The regex in parse is unreadable. Correct, regexes are write-only for most humans. Ask your coach to translate it token by token, then verify the translation by testing two inputs against the behavior it predicts. Translating a regex with an agent and verifying with inputs is exactly how professionals handle other people's regexes.

Where it breaks

The failure mode of reading is believing you did it when you ran your eyes over the lines. Recognition strikes again: syntax you recognize produces a feeling of comprehension that a single concrete trace would puncture. The countermeasure is mechanical: never leave a function without pushing one real value through it. A second failure is reading everything, at uniform depth, until your attention dies twenty minutes before the part that mattered. Depth should follow risk: entry points, boundaries, and anything touching money, auth, or deletion get traced; glue code gets skimmed; generated code gets spot-checked. Attention is your scarcest resource in the agent era. Reading skill is mostly attention allocation.

Knowledge check

Knowledge check

Q1You open an unfamiliar 300-line file. Per this lesson, what do you look at first?
Q2Tracing ms(60000, { long: true }): ms calls format, format calls fmtLong, fmtLong calls plural. What is plural's job in this chain?
Q3The deletion test says: you understand a line when you can name what breaks without it. Why is this a stronger check than 'I can say what the line does'?
Q4An agent hands you a 14-file diff. Applying this lesson's attention-allocation position, what do you do?

Sources

Both traces and both deletion answers in this lesson were checked against the fetched source, not memory. If the file has changed by the time you read it, the moves still apply; report what you find to your coach and trace the current code.