The Documentation Trap
I see this play out on software teams all the time. A developer bumps into a problem. They crack open the official docs, scan the happy path, copy the example—and it flops. Then come the hours of twisting parameters, sifting through Stack Overflow answers from 2014, until finally, in a fit of frustration, they open the source. Five minutes later they spot the bug or the undocumented assumption. The fix is a one-liner. The wasted time? A whole afternoon.
I’m Kai Lindström. I’ve been building software long enough to know that documentation is often a polite lie. It tells you what the author intended, not what the code actually does. Source code tells the truth, every single time. This isn’t a gripe about lazy maintainers—though yeah, they exist. It’s about a fundamental truth hierarchy in our craft: runnable code is the ultimate spec. Everything else is a derivative work that drifts, rots, or stays incomplete.

The Executable Specification
Take a simple Python function with a docstring:
def calculate_discount(price, user_level):
"""
Apply a discount based on user level.
Args:
price: The original price.
user_level: 'basic', 'premium', or 'vip'.
Returns:
The discounted price.
"""
discounts = {'basic': 0.0, 'premium': 0.1, 'vip': 0.2}
if user_level in discounts:
return price * (1 - discounts[user_level])
return price
The docs claim it handles three levels. The code whispers about a fourth, implicit behavior: anything not in that dict gets zero discount. Is that a feature? A bug? The docs don’t say. Only the code knows. If you trust the docs and pass 'admin', you might expect an error or a fallback discount. Instead you get the full price, silently. That’s how subtle data corruption sneaks into production.
Comments Are Not Code
We’ve all stumbled on this gem:
// This function checks if the user is an admin
function isAdmin(user) {
return user.role === 'admin' || user.email.endsWith('@company.com');
}
The comment lies. The function doesn’t just check an admin role; it smuggles in a backdoor based on email domain. If you stop at the comment, you’ve misread the security boundary. Source code is the only artifact the machine actually executes. The machine doesn’t read your Javadoc. It doesn’t care about your README. It runs the bytes. Line up your understanding with the machine’s reality.
When Docs Become Fiction
Open source projects with rapid release cycles are the worst offenders. A library bumps a minor version. The changelog says “updated internal dependencies.” What it doesn’t say is that a previously public method now silently returns a different data type under certain conditions. The docs still show the old return type. Your tests choke on obscure errors. You could waste an afternoon bisecting commits. Or you could read the diff in the source. The diff is the truth. The changelog is marketing.
I once debugged a NoSQL driver whose documentation claimed the connection timeout defaulted to 30 seconds. The actual default, buried in a config class, was 0—meaning no timeout. Production connections hung indefinitely during a network partition. The fix was a one-line change, but the diagnosis required ignoring the manual and reading the constructor. Read the source.

Edge Cases Are the Real API
Documentation usually sketches the golden path. It shows you how to create a resource, update it, delete it. It skips the unhappy paths: what happens when you pass a null, an empty string, a negative number, a ridiculously long input. Source code reveals the conditional branches. It shows you whether the function throws, returns a sentinel value, or silently coerces your input. Those edge cases define the real contract of the system.
Think about a JSON parsing library. The docs say parse(jsonString) returns an object. The source reveals that if the string is empty, you get null, not an empty object. If the string contains a single number, you get a Number, not an object. If you code against the docs, you’ll write typeof result === 'object' checks that fail. Read the source to see the truth table of inputs and outputs.
Performance Characteristics Are Code, Not Prose
Documentation might say “this operation is fast.” Source code shows you the algorithmic complexity. Is it O(n) or O(n²)? Does it allocate new arrays inside a loop? Does it hold a lock? You can’t benchmark every function, but a quick scan of the implementation tells you whether the author thought about performance at all. I’ve seen a “utility” function that sorted a list on every call, even though the list was already sorted. The docs praised its convenience. The source condemned its waste.
Reading Source as a Skill
Plenty of developers find source code intimidating. They treat it like something written by wizards, to be touched only in emergencies. That’s a mistake. Source code is just text—logic encoded in a language you already know. Start with the entry point: the function you’re calling. Trace the arguments. Follow the conditionals. Note the return values. You don’t need to grok the entire codebase. You just need to understand the one path your execution will take.
Use your IDE’s “go to definition” relentlessly. It’s faster than any web search. When you land in minified or transpiled dependency code, step back to the source maps or the original repository. Modern tooling makes this trivial. There’s no excuse. The barrier is psychological, not technical.

Trust Is Earned by Transparency
When I evaluate a new library, I don’t start with the landing page or the quick-start guide. I open the GitHub repo. I look at the source of the core module. I check how errors are handled. I see if there are massive, untested conditional blocks. I gauge the author’s discipline. You can tell more about a library from reading 200 lines of its source than from 20 pages of its documentation. Documentation is a sales pitch. Source code is the product.
This applies to proprietary code too. Internal microservices often have stale wiki pages. The only reliable documentation is the service’s current code and its integration tests. If the tests pass, they demonstrate the actual contract. Read the tests. They’re executable documentation that can’t drift out of sync without breaking the build.
When You Must Read the Docs First
I’m not arguing for zero documentation. High-level architecture, intent, and known limitations have real value. Read the README to understand what problem the library solves. Skim the getting-started guide to see the overall shape. But the moment you need precise behavior—a parameter’s valid range, the order of callback execution, thread-safety guarantees—switch to the source. Docs are a map; source is the territory. The map is not the territory, and when they conflict, the territory wins.
The Debugging Accelerator
Time is the scarcest resource in engineering. Source-code literacy saves it. When a bug surfaces, you can build hypotheses from documentation and test them in production, or you can read the source and know. I’ve watched colleagues write elaborate reproduction scripts for bugs that were obvious from a three-line function body. They trusted the docs. The docs were wrong. The function had a clear off-by-one error visible to anyone who looked.
Learn to love the stack trace. It isn’t a wall of noise; it’s a map of execution. Each frame is a file and a line number. Click through. Read the code at that line. Understand the state. You’ll solve problems in minutes that others chase for days.
Conclusion
The hierarchy is immutable: source code, then tests, then official documentation, then community forums. Invert that order at your own risk. Next time you reach for a library’s docs because something isn’t working, stop. Open the source. Find the function. Read the logic. You’ll become a better engineer—not because you memorized more APIs, but because you started treating software as a transparent system instead of a black box. The code is always honest. Be someone who can hear it.
FAQ
What if the source code is too complex to understand quickly?
Zoom in on the specific path that matters to your problem. You don’t need to grasp the whole architecture. Use your IDE’s navigation to follow only the branches your input would trigger. Complexity is often an illusion created by volume; the single execution path is usually manageable.
Does this mean I should not write documentation for my own projects?
No. Write high-level docs that explain intent, architecture, and non-obvious decisions. But treat them as guides, not specs. Make sure your code is readable and your tests are comprehensive—those are the artifacts other developers will ultimately trust.
How do I handle closed-source or proprietary dependencies where I cannot read the source?
Treat them as high-risk components. Demand thorough documentation and build strong integration tests on your side to pin down behavior. If the vendor can’t offer transparency, you have to build your own safety net through extensive testing and defensive coding.