Notes · Dissecting Real Systems

evergreen

Reading a Codebase

A method for taking real software apart at the source level — find the one type everything hangs from, extract the algebra, and learn what the designers refused to allow.

· · 9 min read

methodology, software-design, reading-code, dissecting-systems

Programs must be written for people to read, and only incidentally for machines to execute.

— Abelson & Sussman, SICP

Cite this
APA
Mangalapilly, Y. J. (2025, January). Reading a Codebase. Saṃhitā Notes. https://yesudeep.com/blog/reading-a-codebase/
BibTeX
@online{mangalapilly2025reading,
          author  = {Yesudeep Jose Mangalapilly},
          title   = {Reading a Codebase},
          journal = {Sa\d{m}hit\=a Notes},
          year    = {2025},
          month   = {January},
          url     = {https://yesudeep.com/blog/reading-a-codebase/},
          urldate = {2026-07-01},
        }
Plain
Yesudeep Jose Mangalapilly. “Reading a Codebase.” Saṃhitā Notes, 2025. https://yesudeep.com/blog/reading-a-codebase/.
RIS
TY  - ELEC
        AU  - Mangalapilly, Yesudeep Jose
        TI  - Reading a Codebase
        T2  - Saṃhitā Notes
        PY  - 2025
        UR  - https://yesudeep.com/blog/reading-a-codebase/
        Y2  - 2026-07-01
        ER  - 

The method behind every other piece in this series, made explicit and reusable. By the end you'll have a repeatable procedure for understanding an unfamiliar system: where to look (and in what order), how to find the one type the whole thing hangs from, how to extract the small algebra underneath the feature list, and why the most revealing question is what the designers refused to allow.

Most advice about reading code is about navigation — set up jump-to- definition, start at main(), follow the call graph. That's how to move around a codebase, not how to understand one. Understanding is a different skill, and it has a shape you can learn. This is the method I use to take a real system apart well enough to write about it — and the same method works whether you're onboarding to a new job, evaluating a dependency, or deciding whether a tool is worth adopting.

Understanding a system isn't memorizing its features. It's finding the small algebra those features are all variations of.

The one rule: the code is the truth

Start here, because it governs everything else. Documentation, blog posts, and your own memory describe what a system intends to do. The source, the issue tracker, and the security history describe what it actually does. When they disagree — and on anything subtle they always do — the code wins.

This is why every claim in these essays cites a file or a line. A mechanism described from memory is a guess wearing a lab coat; a mechanism quoted from source is a fact. The discipline is the whole value.

So the first move on any system is to read the thing, not the README about the thing. Not because docs lie on purpose, but because they describe the design as it was meant to be, and the interesting truths live in the gap between intent and implementation. That gap is where the bugs are, where the clever decisions are, and where the article is.

Where to look, in order

Approach an unfamiliar codebase in four passes, each answering a different question.

  • The source — find the core type. Don't start at main(); start by hunting for the one essential type everything else hangs from. Every system has it. In a build tool it's the node in the build graph; in a database it's the storage engine's record; in a parser it's the input cursor; in a UI framework it's the component. Find that type and the operations on it, and you've found the spine. Everything else is muscle attached to it.
  • The issue tracker — find the pain. Docs state intent; issues state failure. Sort two ways. Most-commented surfaces the pain everyone hits (the rough edges of the happy path). Deep-tail — old, open, unfixed — surfaces the structural bugs that never get closed because they're inherent to the architecture. The second list is gold: it tells you the design's load-bearing compromises.
  • The "why I left" posts. Someone has written "why we migrated off this tool." Their grievance, stripped of its emotion, names the system's true domain boundary — the place where its core assumption stops paying.
  • The security history — find the postmortems. Each past vulnerability is a free postmortem of a design weakness, written by reality. A class of recurring issue isn't bad luck; it's the architecture telling you where it's soft.

Think of it like getting to know a city. The tourist map (the docs) shows where things are supposed to be. But the worn footpaths across the grass, the potholes everyone swerves around, the shops that keep closing in the same spot — those tell you how the city actually works. Issues and incident reports are a codebase's worn paths and potholes.

The four passes in reading order. The source gives you the spine; the issue tracker gives you the pain (sorted two ways); the migration posts give you the boundary; the security history gives you the postmortems.

Extract the algebra, not the features

Now the real work, and the part most people skip. The goal of reading the source is not to produce a feature list — anyone can copy that from the docs. The goal is to find the minimal algebra: the small set of types, operations, and laws the system is really built on.

For any system, name four things:

  • The carrier — the one essential type everything hangs from (you found it in the first pass).
  • The operations — what you can do to that type. The morphisms. (Add a node, query a record, advance the cursor, re-render a component.)
  • The laws — the invariants that must always hold. Determinism. Idempotence. Content-addressing. Acyclicity. These are the load-bearing promises; break one and the system breaks.
  • Essential vs. accidental — what appears in every implementation of this kind of system (essential — that's the algebra) versus what's specific to one tool's language or history (accidental — ignore it).

If eight tools in a domain all implement the same five-line pattern, that pattern is the domain — and everything else is decoration.

The minimal algebra: the carrier, its operations, and its laws form the essential core every implementation shares; the accidental shell — one tool's language and history — is what a reader learns to see past.

The payoff comes when you study several systems in a domain at once and converge. Across all of them, the fixed point — the structure they're each a special case of — is the actual subject. Build tools converge on a dependency graph keyed by content; key-value stores converge on a log-structured merge tree; reactive UIs converge on a dependency-tracked recomputation. Find that shared skeleton and each individual system becomes easy: it's just the skeleton plus a few local choices.

The sharpest lens: what did they refuse to allow?

Here is the single most useful question to ask of any well-designed system, and it is almost never the one people ask. Not "what can it do?" but what did the designers deliberately refuse to let it do, and why?

The best systems are built on a primitive chosen to be general enough to be useful but bounded enough to stay tractable. The restriction is the design.

In each case the limitation is what buys the guarantee. When you find one of these deliberate bounds, you've found the heart of the system. So ask, always: where is the bound here, and what would break if they'd reached for the unbounded version?

A companion question sharpens it further: name the industry default the system rejected. Traditional builds key on file timestamps and a file list; content-addressed builds reject that for a hash and a dependency graph, making cost proportional to change rather than file count. The rejection — the inversion of a default everyone else accepts — is usually the most important thing about the system, and usually the lede.

From understanding to explaining

Reading for yourself ends at the algebra. Reading so you can explain — to a teammate, in a design doc, in an essay — needs one more discipline, and it's the inverse of how most technical writing is ordered. Explain every idea in three movements:

  1. Intuition first. Open with a plain-language framing a smart non-expert could follow — an analogy, or a concrete "imagine ten million files" scenario. No jargon yet. If the reader bounces off the first paragraph, nothing after it matters.
  2. Then the mechanism. Now the real construct: the type, the graph, the protocol, named precisely enough that an expert can go verify it against the source.
  3. Then the consequence. Why it matters — the performance property it buys, the failure it prevents, the whole class of bug it designs out.

Most explanations fail by starting at step 2 — the mechanism — because that's where the writer's own understanding lives. The reader needs step 1 first. Lead with the intuition you arrived at last.

And keep yourself honest while you do it. Quantify instead of gushing — "cost proportional to the change, not the file count" beats "blazingly fast." Name the limits — every system has a domain where it's the wrong choice, and saying so is what makes the praise credible. Carry one thesis through the whole piece, stated early and earned by the end.

The method, distilled

Strip it down and the whole procedure is short enough to keep in your head:

Read the source, not the story. Find the one type everything hangs from. Extract the algebra it converges on. Ask what the design refused to allow. Then explain it intuition-first.

That's the entire method behind this series. It scales from a single library you're evaluating in an afternoon to a domain of a dozen systems you're surveying for a month. The systems change; the procedure doesn't — because every well-built system, underneath its particular surface, is a small algebra with a deliberate bound, waiting for someone to read it out of the source.

Lessons

  • The code is the truth. Docs and memory describe intent; source, issues, and security history describe behavior. When they conflict, the code wins.
  • Look in order: the source (for the core type), the issue tracker (for structural pain), the "why I left" posts (for the domain boundary), the security history (for free postmortems).
  • Extract the algebra, not a feature list: the carrier type, its operations, its laws, and what's essential versus accidental. Across several systems, the convergent fixed point is the real subject.
  • The sharpest question is what the design refused to allow — the deliberate bound that buys a guarantee — and which industry default it rejected.
  • To explain, invert your own order: intuition first, then mechanism, then consequence; quantify, name the limits, carry one thesis.

References

  1. Abelson & Sussman. “Structure and Interpretation of Computer Programs.” — code is for people to read
  2. Catamorphisms.” — and algebraic structures — the "find the algebra" idea, formally
  3. The Build Is Proportional to the Change.” — the method applied to a whole domain
  4. A Language That Can't Loop Forever.” — a worked example of the "what did they refuse to allow?" lens, end to end

How to cite

APA
Mangalapilly, Y. J. (2025, January). Reading a Codebase. Saṃhitā Notes. https://yesudeep.com/blog/reading-a-codebase/
BibTeX
@online{mangalapilly2025reading,
          author  = {Yesudeep Jose Mangalapilly},
          title   = {Reading a Codebase},
          journal = {Sa\d{m}hit\=a Notes},
          year    = {2025},
          month   = {January},
          url     = {https://yesudeep.com/blog/reading-a-codebase/},
          urldate = {2026-07-01},
        }
Plain
Yesudeep Jose Mangalapilly. “Reading a Codebase.” Saṃhitā Notes, 2025. https://yesudeep.com/blog/reading-a-codebase/.
RIS
TY  - ELEC
        AU  - Mangalapilly, Yesudeep Jose
        TI  - Reading a Codebase
        T2  - Saṃhitā Notes
        PY  - 2025
        UR  - https://yesudeep.com/blog/reading-a-codebase/
        Y2  - 2026-07-01
        ER  - 

Annotations

Thank you — your note is held for review and will appear once approved.

Thank you — your note is published.

Please sign in below to leave a note.

Type to search · ↑↓ to move · ↵ to open · Esc to close