Ousterhout: Nobody Decided to Make It This Complicated
Complexity and Understanding
A senior engineer takes a two-line change and spends three days on it. The change is not hard. Before she can make it she has to work out which of the eleven call sites depend on the ordering, why the retry sits in the caller rather than in the client, and whether the flag she is about to set is still read anywhere after the migration that was meant to delete it in 2023. It ships on Thursday. At the retrospective everybody agrees it was a small change.
That is the tax. It is paid by whoever arrives next, it appears on no plan, and nobody ever decided to levy it.
This phase of the series asks three questions about building: what a builder is and what a builder can promise, what a builder has to know, and what holds a team together while it works. Burgess answered the first. John Ousterhout answers the second, and he answers it in a way that makes the question harder and more useful at the same time.
The information question changed shape when we crossed into building. In the learning phase it was whether the truth could travel: whether the organisation could hear what it did not want to hear. In the deciding phase it was whether you were standing at the real place when you chose, or looking at a model of it from four floors up. Both are diagnostic. Both ask what is the case. Building flips the register. The question is no longer whether you can see, but what you must now make visible to the person who comes after you, and what it costs them when you do not.
Ousterhout has spent forty-five years on both sides of that question. He built Magic and Caesar at Berkeley in the early eighties and won the Grace Murray Hopper Award for them in 1987. He created Tcl in 1988 and Tk shortly after, which took the ACM Software System Award in 1997. With Mendel Rosenblum he produced the first log-structured file system, an idea now sitting underneath most of the flash storage in the world. He left for Sun, founded two companies, came back to Stanford in 2008, supervised the work that produced Raft, and at seventy he was submitting patches to the Linux kernel. In 2018 he published a short book called A Philosophy of Software Design, and it has done more to change how good engineers argue about code than anything else this century.
The book is about one thing. Here is the sentence that carries it.
1. Complexity has a definition, and it has nothing to do with size
Ousterhout defines complexity as anything about the structure of a system that makes it hard to understand or to modify.
Read that again, because the definition is doing more work than it appears to. It says nothing about how large the system is. It says nothing about how clever the algorithm is, how many services there are, or how many lines you have. A four-hundred-thousand-line system can be simple. A four-hundred-line service can be a swamp. Complexity is not a measure of the thing; it is a measure of what the thing does to the person trying to work on it.
That makes complexity an information property, which is why Ousterhout holds this lever rather than one of the others.
He gives three symptoms. Change amplification is when an apparently simple change has to be made in many places. Cognitive load is the amount a developer must hold in their head before they can make a change safely. Unknown unknowns is when it is not even apparent what needs to be changed, so you make your change, it passes review, it passes the tests, and something falls over in a region you have never visited.
He calls the third the worst, and he is right. The first two announce themselves. You feel the tedium of change amplification and you feel the weight of cognitive load. Unknown unknowns feel like nothing at all until the incident.
Underneath the symptoms he puts two causes. Dependencies are where a piece of code cannot be understood or changed in isolation. Obscurity is where important information is not obvious. Every symptom traces back to one of them, and both are about information: how much of it a person has to gather, and how hard it is to find.
So the constructive form of the information question in building is this. What must somebody know before they can safely change this, and who pays to find out?
2. It arrives one reasonable decision at a time
Nobody sets out to make a system complicated, and that is why it is so hard to stop.
Each dependency was justified on the day it was added. Each special case had a real reason behind it. Each shortcut was taken by somebody under a deadline you also thought was reasonable, and each of them made a note to come back. Ousterhout’s word for this is incremental, and it is the most important thing he says about the mechanism. Complexity is not created by bad decisions. It accumulates through a very large number of small ones, none of which looks wrong on the day it is made.
Which is exactly why no review catches it. A code review looks at a change. Complexity does not live in the change. It lives in the sum, and nobody is assigned to read the sum.
I have watched this argument play out in a dozen rooms and it always goes the same way. Somebody says the codebase is a mess. Somebody else asks for an example. The example gets defended successfully, because the example was reasonable, and the meeting concludes that the codebase is fine. Everyone leaves knowing it is not fine and unable to prove it. The evidence is distributed across three hundred small choices, and the argument format only admits one at a time.
Now put agents on it.
If complexity accumulates through the rate of individually reasonable decisions, and you have just multiplied that rate, you have multiplied the accumulation. Not the defect rate; the tests still pass. The accumulation. Ousterhout’s own view, given in an interview in April 2025, is that current AI coding tools behave rather like the fastest and least disciplined engineer you have ever employed: quick output, quick fixes, and a bill that lands on everybody else. He does not think this makes design less important. He thinks design becomes the scarce skill, because the volume of code that somebody eventually has to understand has gone up and the number of people available to understand it has not.
That is the discontinuity this phase is built around, stated as precisely as anyone has stated it.
3. The interface is the price
Here is the idea that reorganises everything else.
A module has two faces. The interface is what a user of the module must know to use it. The implementation is what the module actually does. Ousterhout’s move is to treat the interface as a cost and the implementation as a benefit, and to say that a good module is one where the ratio between them is large. He calls those deep modules: substantial functionality, small surface.
The canonical example is Unix file input and output. Five calls, roughly. Open, read, write, seek, close. Underneath them sit disk scheduling, buffer cache management, block allocation, permission checks, journalling, and forty years of accumulated device handling. That interface fits on a postcard. The implementation is a career. That ratio is why the abstraction survived every hardware generation since 1970 while almost everything built on top of it has been replaced twice.
The opposite is a shallow module: an interface that costs about as much to learn as the implementation saves you. A class that wraps a single field with a getter and a setter is shallow. A method that exists to give a name to two lines used in one place is shallow. Ousterhout has a word for the disease of producing them at scale, which is classitis, and his claim is the one that gets him into trouble. Decomposition is not free. Every boundary you draw has to be learned by somebody, and if you draw enough of them for their own sake you will produce more complexity than you removed.
This is where he and Robert Martin publicly disagree, and the disagreement is worth reading in full because both men wrote it together and neither backed down. Martin’s position in Clean Code is that methods should be small, and then smaller. Ousterhout’s is that a method extracted from tightly coupled logic does not remove the coupling, it just spreads it across a dozen stack frames, and now the reader has to hold all twelve. They agree entirely that boundaries matter. They disagree about where a boundary belongs. That argument does not resolve, and it should not, because the answer is different for every module, which is why it takes judgement rather than a rule.
Note what happened to Parnas here. Parnas said, in 1972, that a module should hide the design decisions most likely to change. Ousterhout keeps that and adds a price. Information hiding tells you what belongs behind the wall; depth tells you whether the wall was worth building.
The second edition of the book, published in 2021, pushed this further. General-purpose modules are deeper. A module built for exactly the case in front of you tends to have an interface shaped like that case, which means the next case needs a second interface, and then a third. The slightly more general design usually has fewer total moving parts across the life of the system.
4. Tactical, strategic, and the tornado you promoted
Ousterhout splits programmers by what they are actually optimising for.
Tactical programming takes working code as the goal. Get the feature out, get the bug closed, and design is whatever happens on the way. Strategic programming takes a good design as the goal, and treats working code as necessary rather than sufficient. He is not describing two skill levels. He is describing two objective functions, and the tactical one is the default in almost every organisation because it is the one that gets measured.
Then he names something everyone recognises. The tactical tornado is the prolific engineer who ships faster than anyone, whose output is spectacular, and who leaves a wake that the rest of the team spends years navigating. Management often loves the tornado. From where management sits, the tornado is the most productive person on the floor. The costs land somewhere else, in a quarter that is not this one, on people who cannot name what happened to them.
His remedy is an investment framing. Spend something like ten to twenty per cent of development time on design and cleanup, permanently, as a rate rather than as a project. Not a refactoring initiative. Not a tech debt sprint that gets cut when the roadmap slips. A standing tax you pay on every piece of work, which returns compound interest because every future change gets cheaper.
For a technology leader this is the section that matters, and it is uncomfortable, because the tornado is a creature of the incentive system rather than a character flaw. If you reward visible output and you cannot see accumulated complexity, you will select for tornadoes and then wonder why your delivery rate falls every year. The organisation gets what it pays for. It always did.
Which brings us back to agents. An AI coding tool is a tornado you can rent by the hour, and it has none of the things that eventually slow a human one down. It does not get bored maintaining what it wrote. It does not feel the shame of the pull request that broke Christmas. It will not, on its own initiative, stop and say the design is wrong. Where the whole of your check on complexity was the tacit reluctance of experienced engineers to make a mess they would have to live in, that check has just left the loop.
5. Design it twice
The most practical thing in the book takes one paragraph to explain and most teams never do it.
Before you commit to a design, produce a second one that is genuinely different. A variation will not do. You want a different decomposition, with the boundaries in different places. Then compare them and pick.
Ousterhout is candid that capable people resist this hardest. When you are good at this, your first design is usually decent, and producing a second feels like wasting an afternoon proving something you already know. He points out that this is precisely backwards. The value is not that the second design turns out better, though sometimes it does. The value is that having two makes the first one visible as a choice rather than as the shape of the problem. You cannot evaluate a design you cannot compare with anything.
He tells the story against himself. When he designed the API for the Tk toolkit, the second design was clearly better than the first, and he had been designing systems for a decade at that point.
Simon explains why this works, and the two ideas are the same idea seen from different ends. Rationality is bounded. You do not survey the space of possible designs and select the optimum, because you cannot; you generate candidates until one is good enough and then you stop. Satisficing is not a failure of discipline, it is how minds under constraint operate. Design it twice is a procedural hack that forces the search to continue for one more round after it wanted to stop, and one more round is usually enough to reveal that the first answer was the first acceptable answer rather than the best available one.
There is a version of this with agents that is almost free. Ask for two decompositions, with the constraint that they differ in where the boundaries fall, and then do the comparing yourself. Generation is the cheap half now. Judgement is not, and judgement is what the exercise was always for.
6. Comments are an instrument, not a residue
Ousterhout thinks you should write the comment first, and he thinks so for a reason that has nothing to do with documentation.
The argument runs like this. When you cannot write a short, clear description of what a module does and how to use it, the module does not have a clean abstraction. The difficulty you are feeling while writing the comment is not a writing problem; it is the design telling you something. A comment that requires four sentences of exceptions and caveats is describing an interface that will require four sentences of exceptions and caveats in the head of every person who ever calls it.
So the comment becomes a design instrument, used before the code exists and revised as the design changes. Write it, find it awkward, change the design, watch the comment get shorter.
This puts him directly against the self-documenting code position, and he takes the fight on. Good names help. Good names cannot express why a variable exists, what the caller is responsible for, what invariant is being maintained, or which of two plausible readings of the parameter is the correct one. Those things are not in the code, because they are decisions somebody made about the code, and a decision that is not written down is a decision that will be re-litigated by whoever inherits it.
Under AI this argument gets sharper rather than weaker, and it connects directly to the specification argument this phase opened with. A model is extremely good at telling you what a piece of code does; it will summarise a function you have never seen faster than you could read it. What it cannot tell you is what the code was supposed to do, because that information was never in the code. If the intent lives only in the head of the person who prompted it, and that person has moved on, the system now has a class of unknown unknowns that no amount of reading will resolve. The comment, the specification and the design record are the same artefact viewed at different zoom levels, and they are the only place intent survives.
7. Define errors out of existence
The last idea is the one I find myself using outside engineering more than any other.
The normal way to handle an exceptional case is to detect it and throw. The problem is that every caller now has to know about it, and the exceptional case has propagated into forty places that were otherwise simple. Exception handling is a major source of complexity precisely because it is contagious.
Ousterhout’s alternative is to change the definition so that the case is no longer exceptional. His example is deletion: on Unix you can delete an open file, because the semantics were defined so that the file goes away when the last reference does, and no caller ever has to handle the case. Windows chose to make it an error, and everyone downstream pays. His other favourite is string slicing. Java’s substring throws if the range is out of bounds, so every caller writes a bounds check. Define the operation to clamp the range instead and the exception disappears, along with the checks, along with the bugs in the checks.
The move is not to ignore the error; it is to ask whether the error had to exist.
The organisational version of this is everywhere once you start looking. Every escalation path is an exception handler. Every approval gate that exists for the case where somebody might do the wrong thing is a caller-side bounds check. Every one of them imposes cognitive load on people who will never hit the case, and the honest question is whether the design could have made the bad state unreachable rather than detectable. Sometimes the answer is no. Often it is no because nobody asked.
8. What this lever asks of you now
Pull the threads together and the constructive information question in building comes out as three obligations, and none of them is a dashboard.
The first is to make the cost of understanding visible, because it is the only cost in software that is entirely invisible in the accounts. You can see headcount, cloud spend, and licence fees. You cannot see the four hours a week that every engineer spends finding out how something works before they can touch it. Ousterhout’s symptoms are the closest thing to a measure, and no tool reads them. Take a competent person who did not write the system, give them a real change, and watch what happens. The time between the ticket being picked up and the first line being written is the number. Call that your interface cost, measured honestly.
The second is to pay the tax deliberately. Ten to twenty per cent, as a rate, protected. You pay in this quarter and collect in a later one, and every incentive you have points the other way, so the rate survives only where a leader defends it. If you will not defend the rate when the roadmap slips, do not announce it, because an investment policy that is suspended under pressure is worse than none: it teaches the team exactly what you actually value, and they will believe the lesson.
The third is to insist that somebody reads the sum. Reviews catch changes. Design reviews catch decompositions. If nobody in your organisation has the job of looking at the whole and saying this has got worse, then the incremental mechanism runs unopposed, and it will run faster now than it ever has.
Ousterhout’s position is that none of this changes because the code is generated. What changes is the ratio. The cost of producing code has collapsed and the cost of understanding it has not moved at all, which means understanding is now nearly the whole of the work. Simplicity was always the thing you had to win rather than the thing you were given. It is just that you used to be able to postpone the fight.
(An Organisational Prompt is something you can do now....)
Organisational Prompt
Watch somebody try to change something they did not build.
Pick a service that matters and is not new, then find a competent engineer who has never worked on it. Give them a small, real change, the kind that would normally be sized at half a day. Then sit next to them, say nothing, and write down every question they have to answer before they can write the first line.
Do not help. Do not explain. Your job is to record.
You will have a list of between eight and thirty questions. That list is the real interface of your system: it is what a person must know before they can act, and it is the tax every change has been paying. Read it and mark each item as one of two things. Obscurity, where the information exists but is not findable. Dependency, where the information exists somewhere else and this thing cannot be understood alone.
Then take the single most expensive item and ask the question that closes the loop: did this error have to exist? Not how do we document it better. Whether the design could have made the question unnecessary.
Run it again in three months with a different person and a different service. If the list is longer, your rate of accumulation is beating your rate of investment, and you now have the one piece of evidence that argument has always lacked.
Further Reading
Ousterhout’s book page carries a free extract of the second edition, including the new material and his comparisons with Clean Code: web.stanford.edu/~ouster/cgi-bin/aposd.php.
The full dialogue with Robert Martin on method length, comments and test-driven development, jointly edited by both authors: github.com/johnousterhout/aposd-vs-clean-code.
His fullest recent statement on AI and design, with a transcript: newsletter.pragmaticengineer.com/p/the-philosophy-of-software-design.
Raft, the consensus algorithm Ousterhout and Diego Ongaro published in 2014. Understandability was its stated design goal, which is the clearest evidence that he applies the argument to his own research; paper, lectures and visualisations at raft.github.io.
The log-structured file system paper with Mendel Rosenblum: people.eecs.berkeley.edu/~brewer/cs262/LFS.pdf.
I write about the industry and its approach in general. None of the opinions or examples in my articles necessarily relate to present or past employers. I draw on conversations with many practitioners and all views are my own.

