I picked up this habit years ago while wrestling a C library whose documentation promised elegance and delivered riddles. The source is the truth. Docs are a translation—sometimes faithful, often not. If you actually want to know how a piece of software behaves, you crack open the implementation. Not the README. Not the wiki. The code. This isn’t about distrusting people; it’s about respecting the one artifact that compiles.
I’ve lost count of the times Javadoc lied to me while the bytecode told a different story. Prose drifts. Interfaces shift under the maintainers’ feet, writers jump to other projects, and that docstring from three releases ago petrifies into a little archaeological fib. The function body, meanwhile, just sits there—unflinching. It’ll tell you what it allocates, what it hands back, and which edge case it silently swallows. This post makes the case for flipping the habit: source first, docs as the companion—not the compass.

The Documentation Trap: When Prose Becomes Propaganda
A while back I inherited a Python SDK that wrapped a REST API. The official docs swore the client’s create_resource() method accepted a timeout parameter. I passed it. Nothing happened. Somebody had copied the API reference without checking whether the parameter was actually threaded through. Fifteen minutes in the source told the real story: the constructor ignored that kwarg completely. Dead code in the documentation—a phantom feature that lived only in bullet points.
This isn’t a fluke; it’s routine. Documentation captures intent, not execution. It’s written by people who might have parachuted into the project mid-flight, who may be summarizing for a totally different audience, or who are simply wrong about what the code does in the unhappy-path weeds. The source doesn’t have those distortions. It’s the executable specification.
I’m not tossing docs in the bin. Their real value kicks in after you’ve built a mental model from the source. Good docs give you the why and the architecture. Bad docs hand you misinformation. The only way to tell them apart is to already know what the code says.
An Example from the Trenches: The Missing Semaphore
Here’s a snippet I audited in an internal library a few years back:
// Doc: Acquires a lock and executes the callback safely.
void execute_locked(Callback cb) {
lock.acquire();
try {
cb.run();
} finally {
lock.release();
}
}
The documentation read clean. The method signature looked safe. But I noticed something in the source: lock.acquire() had no timeout. If a dead thread held that lock, the call blocked forever. The docs never whispered a word about it. They couldn’t—the author hadn’t thought about it. Only the source showed the unbounded wait. I patched it with a try-lock pattern in ten minutes, time I would have lost in production if I’d trusted the paragraph above the function.

Why Reading Source Changes Your Debugging Instincts
Engineers who make a habit of starting with source develop a different sense for trouble. They stop asking “what does this function do?” and start asking “what does this function actually do?” That shift matters. It’s the gap between debugging by printf and reasoning about state transitions. The source hands you the state machine; the docs hand you the sales pitch.
Look at memory allocation in C++ libraries. Documentation tells you “returns a newly allocated object.” Great. But the source shows whether it uses new, malloc, a pool allocator, or some custom arena. That distinction tells you if you need to call delete, free, or a factory-supplied destructor. Guess wrong, and you leak. Docs rarely capture this detail because they’re written for the 80% use case. The source is written for the machine, and the machine cares about 100% of the cases.
I’ve watched junior developers lose days chasing bugs a thirty-second peek at the source would have explained. A function claimed to return a list; it returned an immutable list. The docs said “list.” The code said Collections.unmodifiableList(). The exception blew up ten frames later, in code that had nothing to do with the original operation. Read the source first, and you don’t waste time building on false premises.
Source-Driven Learning: The Fastest Path to Mastery
When I pick up a new framework, I skip the getting-started guide. I clone the repo, find the core interface, and trace a call from entry point to output. Takes maybe an hour, but it teaches me more about the architecture than a week of tutorials. I see the design patterns the authors actually used—not the ones they documented. I see the threading model, the error-handling strategy, the assumptions about immutability. Once I have that map, the documentation clicks because I know what it’s glossing over.
Recently I dug into a Go-based message queue. The docs bragged about “at-least-once delivery.” The source showed me the deduplication mechanism was just an in-memory set, no persistence. Meaning “at-least-once” degraded to “at-most-once” across restarts. I wouldn’t have caught that from the marketing page. I caught it by reading broker.go.

When Documentation Is Actually Dangerous
There’s a breed of documentation I call “zombie docs”—pages that outlive the code they describe. They squat in wikis, Confluence, even official project sites, and they’re wrong. Not slightly wrong. Wrong. They describe parameters that got removed, return types that changed, security guarantees that were never implemented. Read them first, and they poison your understanding. You’ll write code against an API that doesn’t exist and blame yourself when it fails.
I’ve seen this in cryptographic libraries. Docs say “uses AES-256-GCM.” Source says cipher.init(Cipher.ENCRYPT_MODE, key, new GCMParameterSpec(96, iv)) but the IV is hardcoded. That’s not encryption; it’s a deterministic transformation. Trust the docs, and you’re shipping broken security. Read the source, and you’re horrified and immediately filing a bug.
Source-first reading is a security practice. It’s a reliability practice. It’s a professional obligation when you’re weaving third-party code into a system people count on. Docs are a press release. Source is the product.
How to Read Source Code Effectively (Without Getting Lost)
I’m not suggesting you read every line of a million-line codebase. That’s nuts. I use a targeted approach:
- Start at the interface you’re calling. If it’s a library, find the public function or method. Trace its arguments through the first few levels of indirection.
- Look for side effects. File I/O, network calls, lock acquisitions, global state mutations. These are the things docs often omit.
- Check error paths. What happens when the input is null? When the network is down? When allocation fails? The source tells you; the docs rarely do.
- Identify assumptions. Thread-safety, immutability, ordering requirements. Often implicit in the source but absent from the prose.
- Use the tests as a secondary source. They’re executable documentation that shows expected behavior under specific conditions.
This takes practice, but it compounds. After a few months you’ll read code faster than documentation because you’ll know what to look for and what to skip.
Documentation’s Real Role: The Map After You’ve Seen the Terrain
I don’t discard documentation. I use it as a high-level map after I’ve walked the streets. Once I understand the source’s structure, the docs fill in context: why certain decisions landed where they did, what the intended use cases are, how the authors think about the problem. That’s valuable. But it’s supplementary. It’s the commentary track on a film you’ve already watched.
Think of it this way: documentation is written for humans, and humans are fallible. Source is written for compilers and interpreters, and they are unforgiving. When you need precision, you go to the unforgiving thing. When you need narrative, you go to the fallible thing. But you never confuse the two.
I’ve worked on teams where “read the docs” was the standard answer to questions. I’ve also worked on teams where “link me to the source” was the first reply. The latter teams shipped faster, broke fewer things, and had fewer production incidents. Correlation isn’t causation, but I’ve seen the pattern enough to trust it.
FAQ
Isn’t reading source code slower than reading documentation?
At first, yes. But documentation can mislead, and the time lost debugging a misunderstanding often dwarfs the time spent reading code. Once you’re fluent in a language, scanning source for a specific behavior is very fast—often faster than searching through poorly organized docs. The investment pays off in reduced debugging time and fewer integration surprises.
What if the source code is poorly written or unreadable?
That’s a signal in itself. If the source is a mess, the library is probably fragile, no matter what the documentation claims. You might decide not to depend on it, or you might wrap it to isolate the chaos. Either way, you’re making an informed decision that documentation alone wouldn’t support.
Should I always read the source before the docs, even for simple libraries?
For simple, well-established libraries with stable interfaces, the documentation is probably solid. But even then, a quick glance at the source can reveal edge cases or performance quirks the docs gloss over. I’d say: make source-first a habit for any code you’ll lean on in production. For exploratory work, docs are fine as a starting point, but verify critical assumptions in the source.
How do I convince my team to adopt source-first reading?
Lead by example. During code reviews or bug hunts, cite specific lines of source from dependencies to explain behavior. Share instances where documentation was misleading. Over time, the team will see the value. You don’t need a policy; you need a culture of curiosity about what’s actually executing.