Reading Other People’s Code — How to Do It Faster Than You Wrote Your Own

Reading other people’s code is a skill that separates juniors from seniors more than any framework knowledge ever will. Every developer writes. Few really know how to read.

You get a repository with 200,000 lines, 47 microservices, and documentation from 2019. You open the IDE, click a random file, scroll, close it. You open another. An hour later you still don’t know what the code does — you only know you don’t want to touch it.

The problem isn’t that the code is complicated. The problem is that you’re reading it wrong.


The myth of “good code that documents itself”

You’ve heard it dozens of times: “good code is self-documenting.” That single phrase has caused more damage to developer careers than all of Stack Overflow combined.

No non-trivial system documents itself. A variable name tells you what the variable holds. It doesn’t tell you why that variable exists in the first place, who modifies it, when its value makes sense, or what business problem forced its existence. That context lives outside the code — in decisions, constraints, and the team’s history.

Developers who believe the self-documenting code myth read someone else’s project like a novel: line by line, file by file, top to bottom. An hour in, they have a thousand details in their head and zero understanding of the system. That’s the exact opposite of the direction you should be going.


Reading other people’s code starts at entry points, not at the file that caught your eye

Every system has entry points — places where execution begins. They’re the only spots that give you full context. Everything else is a fragment torn from a puzzle.

Typical entry points by project type:

  • Web backend: route/endpoint definitions — these accept requests from the outside world
  • CLI: the main() function or its equivalent in your language
  • Worker/scheduler: definitions of cron jobs or queue consumers
  • Frontend: the root component (App.jsx, main.ts) and routing
  • Library: the public API in index.* or the package manifest

You open the entry point and ask one question: “What does this system do from the outside?” Not how it does it. What. Only when you have that answer have you earned the right to go deeper.


Map the system before you understand a function

Second rule: never try to understand a specific function before you understand the context it’s called in. This is the trap 90% of developers fall into.

You open processOrder(). 200 lines, 12 variables, 4 nested ifs. You start reading from the top. Five minutes in you’ve lost the thread. You go back to the beginning. Fifteen minutes in you still don’t know what the function does.

Do the opposite instead. Close the file and find every place that calls processOrder(). Each callsite is a specification — it shows what inputs the function is called with, what system state it expects, and why. Three callsites will give you more context than reading the function body for an hour.

This is reading other people’s code the way seniors do it: top-down instead of bottom-up. From context to detail, never the other way.


Tools in order of use: grep, ctags, LSP

Most developers navigate code exclusively through IDE clicks. That’s like reading a library by turning every page in sequence. It works, but it’s needlessly slow.

grep — your first reflex

Before you open anything, check whether the term you’re hunting for even exists and how often it appears:

# How many times "processOrder" appears in the project
grep -r "processOrder" . --include="*.js" -c

# All function definitions containing "Order"
grep -rn "function.*Order\|def.*Order" .

# Files that import a given module
grep -rln "from.*OrderService" .

grep -r across a project directory will give you in 200 ms what an IDE renders in several seconds. Learn the flags: -n (line numbers), -l (filenames only), -c (count matches), --include (filter by file type).

ripgrep (rg) — grep for modern projects

If you work with a large repository, grep is slow and doesn’t understand .gitignore. ripgrep solves both — it’s 5-10x faster and automatically skips node_modules, vendor, build:

rg "processOrder" --type js
rg -A 5 "class OrderService"  # 5 lines of context after each match

LSP in your IDE — when you need semantics

Only once you know where to look should you reach for IDE features built on the Language Server Protocol: “Find References,” “Go to Definition,” “Find Implementations.” LSP understands types and scopes, so it won’t confuse you like grep can with names that recur across different contexts.

Order matters: grep to locate the area, LSP to understand the semantics. Reversing this order wastes time, because LSP in a large project takes seconds to load.


Git as a reading tool, not just a commit tool

Someone else’s code isn’t just the current state of files. It’s also a history of decisions visible through git log and git blame.

When you see a strange line of code that “looks like a bug” — before reporting or fixing it, check why it’s there:

# Who wrote this line and when
git blame -L 45,55 path/to/file.js

# History of a specific function (tracks changes)
git log -L :processOrder:path/to/file.js

# All commits touching the file, with full diff
git log --follow -p path/to/file.js

In 70% of cases, “the strange line” turns out to be a deliberate fix for a bug you can’t see. The commit message or ticket reference in git history will tell you why. Without that knowledge, your “fix” will restore the old bug.


When to stop reading and start running

Static code reading has a ceiling. Past a certain complexity, no mind can hold every state the code might reach. At that point reading stops being productive.

The solution: run the code. But not just anyhow.

  • Set a breakpoint at the entry point and step through a real request with a debugger
  • Add temporary logs at 3-4 strategic points to see the order of calls
  • Run the existing tests with the verbose flag — they’ll show you how the code behaves in different scenarios
  • If the code talks to a database, check what queries it generates (e.g. via EXPLAIN ANALYZE or a query log)

Five minutes with a running debugger will give you more understanding than an hour of staring at files. I covered the same approach in the article on debugging like a developer — a debugger is a tool for deduction, not just a tool for catching errors.


What this article doesn’t cover — and why

You won’t find a list of “best practices” here for naming variables, when to use comments, or how to format code to make it “readable.” Reading other people’s code isn’t a style problem — it’s a strategy problem.

You might inherit a project written in a style you hate: global variables, 500-line functions, names transliterated from another language. None of that changes the process. Entry points, callsites, tools, git history, running the code — it works regardless of the quality of code you’re reading.

In fact, reading code whose style annoys you is the best training for this skill. It’s easy to read code that looks like your own. Professionalism starts where the code is ugly and you have to understand it anyway.


What to do differently starting today

Next time you get a new project — or revisit your own from two years ago — before opening a single file, do three things:

  1. Open the README and the package manifest. Identify the project type and its entry points.
  2. Find the entry points physically in the code. Read only them — not the functions they call.
  3. Pick one execution path (e.g. a single endpoint) and trace it top-down, using grep and LSP as your map.

Reading other people’s code is a skill without a ceiling. The more code you read in this structure, the faster you recognize architectural patterns — and the less time you need for every next project. It’s one of the few skills in programming where experience actually compounds.


Piotr Karasiński
Piotr Karasiński — self-taught of software, GNU/Linux and systems architecture enthusiast. Writes about the layer between "it works" and "I understand why it works" at devmindset.dev.

Leave a Comment