The counterweight to Clean Code: deep modules, simple interfaces, by a Stanford professor.
Why this book
Once you've read Clean Code, you eventually run into Ousterhout's name. His is the book people quote to contradict you: where Robert Martin preaches three-line functions, Ousterhout demonstrates, with examples, that chopping code too finely makes complexity worse, not better. The two books disagree, and that's exactly why you should read both.
Ousterhout isn't a consultant: he's a Stanford professor who built real systems (the Tcl language, the log-structured file system, the RAMCloud storage system). The book grew out of his software design course, where students write a program, critique it, then rewrite it. The result: 196 pages, zero filler, and a single obsession from start to finish, complexity. He warns you himself: "every problem described here is one that I have experienced personally, and every suggested technique is one that I have used successfully in my own coding."
The ideas that stick
The book runs 196 pages across 22 short chapters. Here is what stays with you once it's closed.
1Complexity has a definition, and it's sneaky
Ousterhout refuses to talk about complexity "in general". He defines it: anything that makes a system hard to understand and modify. It shows up three ways:
- Change amplification: changing a banner's color forces you to edit every page that copies it.
- Cognitive load: everything a developer must hold in their head to touch one line without breaking something.
- Unknown unknowns (the worst): you don't even know which piece of code needs changing, you find out when the bug appears.
Only two things feed all of this: dependencies and obscurity. And the line that hurts: "complexity is more apparent to readers than writers" (p. 9). The person who writes the code doesn't feel it; the one who inherits it does.
2A good module is deep
This is the core idea of the book, and the sharpest counter to Clean Code. A deep module packs a lot of functionality behind a simple interface: "the best modules are those whose interfaces are much simpler than their implementations" (p. 22). The perfect example: Unix's five I/O calls (open, read, write, lseek, close). Five trivial signatures hiding hundreds of thousands of lines (permissions, disk caches, scheduling, drivers).
The opposite, a shallow module, has an interface almost as complicated as what it does: it costs you in learning without saving you anything. Ousterhout names the disease: "classitis", the belief that "classes are good, so more classes are better". Stacking one-method mini-classes simplifies nothing, it just adds interfaces to learn. That's why he stands flatly against the cult of the tiny function.
3Tactical vs strategic: beware the tornado
Working code isn't enough. The tactical programmer focuses on the next feature, ships fast, and piles up complexity with every shortcut. The strategic programmer treats good design as the goal, not a bonus.
Ousterhout describes a figure everyone has met: the "tactical tornado", the ultra-productive developer who pumps out code faster than anyone, adored by management, and who leaves behind "a wake of destruction" that others spend months cleaning up. His advice: invest 10 to 20% of your time in design, continuously. Because technical debt, unlike a bank loan, is almost never repaid.
4Hide information, don't decompose by time
The main way to get deep modules: information hiding, a principle David Parnas already laid out in 1972. Each module encapsulates a design decision that doesn't show in its interface (a file format, a network protocol, a tree structure).
The opposite mistake is temporal decomposition: structuring code by order of execution. You read a file, you modify it, you write it back, so you create three classes: Read, Process, Write. Except the class that reads and the one that writes both need to know the file format. The same decision lives in two places: that's a leak, "one of the most important red flags in software design" (p. 31). The day the format changes, you edit two classes instead of one. The right answer: a single module that knows the format, full stop.
5Pull complexity downwards
Faced with unavoidable complexity, you have two choices: handle it inside the module, or push it onto everyone who uses it. Ousterhout is blunt: "it is more important for a module to have a simple interface than a simple implementation" (p. 61). In other words, "it is better for the developers to suffer than the users" (p. 63): one team absorbs the difficulty inside, rather than a thousand callers suffering it outside.
A concrete example, in code:
# ✗ complexity PUSHED out: every caller has to guess the value
client = Client(retry_interval=0.5) # 0.5? 2? nobody knows how to tune it
# ✓ complexity PULLED in: the module measures real times and decides
client = Client() # bare interface, one team handles it, a thousand callers relieved
6Define errors out of existence
The most original and most underrated idea in the book. Exception handling is "one of the worst sources of complexity". The best defense isn't catching them better: it's redefining the semantics so the error can't happen.
His own confession: designing the Tcl language, he made deleting a nonexistent variable raise an error. The result, everyone wrapped the call in a catch to ignore it. The right definition: "ensure that a variable no longer exists". With that, deleting an absent variable becomes normal, the error is gone. Same logic for Java's substring, which throws if the index exceeds the length, forcing you to write guard checks everywhere. Python chose the opposite: an out-of-range slice simply returns an empty result. No exception, no guard code, a deeper method.
// Java: throws IndexOutOfBoundsException, needs guards everywhere
"hi".substring(0, 10); // 💥 exception
# Python: out-of-range slice = empty result, no error
"hi"[0:10] # → "hi"
7The head-on clash with Clean Code
This is where the two books collide. Ousterhout quotes Robert Martin word for word: "the first rule of functions is that they should be small. The second rule of functions is that they should be smaller than that." His answer: past a certain size, cutting further helps no one.
// ✗ split too finely: you can't grasp one without jumping to the other
function readPrice(c) { return c.price } // "conjoined methods"
function withVat(c) { return readPrice(c) * 1.2 }
// ✓ one "deep" function: self-contained, readable in one block
function priceWithVat(c) { return c.price * 1.2 }
Functions split too finely lose their independence and become conjoined methods (you can't understand one without reading the other), which is worse than one slightly long function. His line: "depth is more important than length".
Same fight over comments. Martin writes "comments are always failures"; Ousterhout is a fierce defender of comments, because they capture what code can't: the abstraction, the intent, the why. He mocks Martin's fix (replace a comment with a very long method name), which ends up producing names like isLeastRelevantMultipleOfNextLargerPrimeFactor.
He goes further: write the comments first, before the code. Not for documentation, as a design tool. If a comment gets long and painful to write, it's "the canary in the coal mine": a sign your abstraction is bad.
8The opinions that sting, and the red flags
The book's most contrarian chapter runs every trend past a single question: does it reduce complexity? The verdict is sharp. Against Test-Driven Development (except to fix a bug): "it focuses attention on getting specific features working, rather than finding the best design. This is tactical programming pure and simple". Against getters and setters, "shallow methods that clutter the interface without providing much functionality". Wariness toward design patterns, whose real danger is over-application: don't force a problem into a pattern when a custom solution would be cleaner.
And above all, the book's practical gift: its list of red flags, usable in code review tomorrow:
- shallow module;
- information leakage;
- temporal decomposition;
- pass-through method;
- conjoined methods;
- comment repeats the code;
- vague name;
- nonobvious code.
When one of these signals shows up, stop: there's probably a hidden design problem behind it.
Three things I didn't know
- The hardest bug of his career came from a name. In the Sprite operating system (late 1980s), the variable
blocksometimes meant a physical block on disk, sometimes a logical block within a file. One single spot confused the two and overwrote blocks at random. Six months of hunting. WithdiskBlockandfileBlock, the bug would never have existed. - "Design it twice" (ch. 11) targets bright developers specifically. Growing up, he says, smart people learn that their first idea is good enough for a good grade, and never learn to compare two alternatives. It's a bad work habit, not a talent.
- Every example comes from real systems: his students' projects at Stanford, and RAMCloud, his own research system. When he refactors a Buffer class in chapter 20, he gives the actual numbers: 8.8 ns → 4.75 ns, and 20% less code. The clean code was also the fastest.
My take, honestly
To me, this is the best software design book of the last twenty years, and by far the most pleasant to read. Where Clean Code is verbose and dogmatic, Ousterhout is short, clear, and argues instead of asserting. The deep-module idea changed how I judge a class: I no longer count lines, I look at the ratio between what the interface costs to learn and what it gives me back.
What keeps it from being perfect: the examples. They almost all revolve around the same two or three cases (a text editor, the RAMCloud system), and it smells of the lecture hall. You wish for more real-world code, more variety. And some ideas, like "make your modules deep", are easier to understand than to apply: they take judgment, not a recipe. It's not a checklist, it's a way of thinking, which makes it both deeper and less immediate than Clean Code.
The right move is to read both and make up your own mind. Where they contradict each other, on function size, on comments, Ousterhout is almost always right.
Odilon
Still relevant in 2026?
More than ever, for a reason nobody anticipated. The book depends on no language and no trend: it's about complexity, the one problem that will never go away. And at a time when AI generates a good share of the code, telling a deep module from a shallow one becomes a superpower. AI happily produces chatty interfaces and shallow classes; that's exactly what this book teaches you to spot and reject. Ousterhout's grid is precisely the critical eye a machine that outputs plausible code is missing.
Who is it for?
Read it if
- You've read Clean Code and want to hear the other side, better argued
- You design libraries or APIs and want to make them simple to use
- You inherit code that "works" but nobody dares touch, and you want to name why
- You review AI-generated code and want criteria to judge an interface
Skip it if
- You're a complete beginner: learn to code first, design comes after
- You want ready-to-copy recipes: this is a book of judgment, not a catalogue
- You want varied examples from the modern web: here it's the text editor and RAMCloud
Going further
Several ideas from this book can be practiced in my free courses: splitting responsibilities and designing interfaces in the OOP course, naming and structuring in the testing course, and the reflex of reviewing generated code in Coding with AI.
Comments (0)