HomeProgrammingHow to Read Other People's Code: The Skill Nobody Teaches

How to Read Other People’s Code: The Skill Nobody Teaches

Date:

The Skill That Matters More Than Writing Code

Professional software developers spend more time reading existing code than writing new code. Joining a new team means reading a codebase that may have been built over years by many developers with different styles, levels of experience, and varying documentation habits. Debugging means reading code that isn’t working and understanding why. Code review means reading another developer’s code and evaluating its correctness and quality. The ability to read code effectively — to understand what it does, why it was written that way, and what the implications of changing it are — is the skill that determines how quickly a developer becomes productive in any unfamiliar codebase.

This skill isn’t explicitly taught in most programming curricula, which focus on writing code from scratch. The transition from student or self-taught developer to professional developer is significantly harder for this gap than for any gap in specific language knowledge.

Starting at the Right Level of Abstraction

The mistake that makes reading unfamiliar code most frustrating: starting at the wrong level. Reading a complex function line by line before understanding what that function is supposed to do, where it’s called from, or what problem it solves produces confusion rather than understanding. The right reading sequence goes from high-level to low-level: understand the overall system architecture, then the module or file’s purpose, then the class or function’s role, then the specific implementation.

Practical starting points for an unfamiliar codebase: the README (for overall purpose and structure), the entry point (main.py, index.js, App.jsx — wherever execution starts), the routing or URL configuration (for web applications, this shows what endpoints exist and what handles each one), and the test files (tests often describe what a function is supposed to do more clearly than the implementation itself). This high-level reconnaissance before diving into specific implementation details produces a mental model that makes the details interpretable when you reach them.

Using the Tools That Help

Modern IDEs and code editors provide capabilities that make unfamiliar code more navigable: Go to Definition (jumping to where a function or class is defined from a call site), Find All References (seeing everywhere a function or variable is used), Call Hierarchy (visualising what functions call a specific function and what it calls), and symbol search (finding any function, class, or variable by name across the entire codebase). Developing the habit of using these navigation tools rather than scrolling through files manually makes reading unfamiliar code dramatically faster.

Git history is an underused resource for understanding why code looks the way it does: `git log –follow -p filename` shows the change history for a specific file, including the commit messages that explain why each change was made. The code that looks like an inexplicable special case often has a git commit with a message like ‘fix edge case for users with billing address in the US territories’ that explains both the why and the what.

The Print Statement Debug for Understanding

One of the fastest ways to understand what code is doing when reading it isn’t sufficient: run it and observe the output. Adding print statements (or using a debugger with breakpoints) to trace what values variables hold at specific points in the execution is the empirical complement to reading-based understanding. The function that’s confusing when read often becomes clear when you can see what it’s actually receiving as input and producing as output.

This ‘read then run’ approach works best when you’ve already formed a hypothesis about what the code should be doing and you’re using execution to confirm or refute it. Running code without a hypothesis about what you expect to see produces observations without framework; running it to test a specific expectation produces either confirmation or the specific deviation that clarifies what’s actually happening.

The Documentation That Exists in Tests

Test files are often the best documentation in a codebase: they describe what a function is supposed to do (the test name), what inputs it receives (the test setup), and what outputs it should produce (the assertions). Reading the tests for an unfamiliar function before reading the function implementation provides the specification that the implementation is supposed to fulfil — a context that makes the implementation significantly more interpretable.

The absence of tests is itself informative: code without tests has no executable specification and may have behaviour that wasn’t intentional but has become relied upon. In codebases without tests, the combination of reading, running, and asking the people who wrote the code (if available) is the available approach. The questions worth asking: ‘What is this supposed to do?’ and ‘What would break if I changed this?’ produce more useful answers than ‘How does this work?’ which tends to produce implementation walkthroughs rather than understanding of intent and constraints.

Related stories

Containerisation with Docker: What It Solves and How to Get Started

The Problem That Containers Solve 'It works on my machine'...