Practical Coding Rounds/Debugging Unfamiliar Code

Orienting Fast in a Strange Codebase

Using the codebase's own tests, git history, and logging as documentation you didn't have to write — the fastest way to build a mental model of code you've never seen, without reading every file.

!!!2/5Theory: 20m

The problem this solves

Give an experienced engineer an unfamiliar few-thousand-line codebase and a clock, and the difference between a fast, confident orientation and a slow, lost one usually isn't raw reading speed — it's which artifacts they read first. Reading files top to bottom in directory order is the slowest possible strategy; the codebase almost always contains faster paths to the same understanding, left there by whoever wrote it, whether or not that was the intent.

Tests are documentation you didn't have to write

The single highest-leverage thing to read first, before any implementation file, is the existing test suite — specifically, the tests nearest to whatever area the bug report or feature request touches. Tests encode intended behavior as concrete input/output pairs, which is a much faster way to build a correct mental model than inferring intent from implementation code, and it sidesteps a real trap: implementation code shows you what the system does, which is exactly the thing that might currently be wrong. A test file failing in a way that doesn't match its own name or the behavior it's asserting is frequently the fastest lead of the entire round.

Reading order that tends to work well under time pressure:

  1. Test names and top-level describe/class groupings — free semantic map of "what this module is supposed to do," before reading a single assertion.
  2. The specific test(s) nearest the reported symptom, read in full.
  3. Only then the implementation file(s) those tests exercise — now you're reading with a hypothesis already in hand instead of cold.

Git history is a second, independent source of intent

git log --oneline -- path/to/file and git blame answer a different question than the code itself does: not "what does this do" but "why does it look like this, and when did it last change." A few concrete uses that come up constantly in bug-squash and repository-extension rounds:

  • If the bug report mentions a recent regression, git log on the suspicious file(s) filtered to recent commits is often faster than any amount of reading.
  • git blame on the specific line that looks wrong surfaces the commit message and, often, a linked ticket or PR description explaining the intent behind that line — intent that isn't otherwise visible in the code itself.
  • git bisect automates the "which commit introduced this" search as an actual binary search over the commit range, rather than eyeballing a long diff — genuinely faster once a range of "known good" and "known bad" commits is established, and a legitimate tool to reach for live in an interview if the situation calls for it.

Reading for structure before reading for logic

Before diving into any single function's implementation, spend two or three minutes building a coarse map: what are the top-level modules/packages, and what's each one's one-sentence responsibility? Most codebases signal this through directory naming (models/, services/, handlers/, repositories/) even without documentation — and knowing the map means that once you do form a hypothesis about where a bug lives, you can jump there directly instead of searching by scrolling. A fast, low-tech way to build this map: rg (ripgrep) or your editor's project-wide search for the specific error message, field name, or function name mentioned in the bug report — this alone frequently narrows a few-thousand-line codebase down to the two or three files that actually matter, in under a minute.

Logging as a live debugging tool, not just a reading aid

Once you've narrowed to a specific area, adding a small number of targeted log statements or breakpoints at the boundary between "known correct" and "known wrong" state is almost always faster than continuing to read silently. This is the live, dynamic complement to the static reading above: reading tells you what the code is supposed to do, targeted logging tells you what it's actually doing on this specific input, right now. The two together — a hypothesis from reading, confirmed or refuted by a log line — is the fast version of the reproduce-isolate-hypothesize loop from the previous subtopic, compressed into the orientation phase itself.

Further Resources (Optional)