John Ousterhout, professor of computer science at Stanford and author of A Philosophy of Software Design, discusses the state of software design, the impact of AI on engineering, and practical strategies for managing complexity in software systems. He draws on decades of experience in both academia and industry — including co-founding companies, creating the Tcl scripting language, and inventing the Raft consensus algorithm — to argue that software design is the most critical and under-taught skill in computer science, and that its importance will only grow as AI tools absorb more low-level coding tasks.
The growing importance of software design in the age of AI
AI tools are rapidly improving at generating low-level code — autocomplete will produce increasingly high-quality implementations, reducing the time engineers spend on tactical coding.
The big open question is whether AI can replace higher-level design work; so far, Ousterhout sees no evidence that it can.
As AI handles more implementation, software design will become the dominant activity for developers — making it even more problematic that universities barely teach it.
Ousterhout defines software design as a decomposition problem: how to break a large, complex system into smaller, relatively independent units that can be implemented and understood in isolation.
He considers decomposition the single most important idea in all of computer science.
Tactical tornadoes and the cost of short-term speed
A tactical tornado is a prolific programmer who pumps out code quickly but leaves a wave of destruction — poor design, technical debt, and cleanup work for others.
These individuals are often celebrated in organizations that value short-term speed, especially startups.
Ousterhout distinguishes tactical tornadoes from what he considers true 10x engineers: people who produce clean, minimal designs that deliver more functionality with greater stability and evolvability — sometimes by writing less code.
The perception of who is “10x” depends on vantage point: non-technical managers may mistake a tactical tornado for a top performer because they only see output volume, not the downstream costs.
Deep vs. shallow modules: the key lever against complexity
A deep module provides a simple interface but hides significant internal complexity and functionality — this is the ideal, because it minimizes cognitive load for callers while encapsulating powerful behavior.
A shallow module has a wide or complex interface relative to the little functionality it provides — it fails to hide complexity and forces users to understand its internals.
Ousterhout’s entire framework for software design derives from the goal of managing complexity: either eliminating it entirely (e.g., by removing special cases) or hiding it behind deep modules so most of the system doesn’t have to confront it.
Designing it twice: why your first idea is rarely the best
Ousterhout observed that top students at Stanford and Berkeley often latch onto their first idea because their academic careers rewarded quick, “good enough” solutions — but on truly hard problems, the first idea is never optimal.
He forces students (and himself) to design twice: come up with a second, genuinely different approach even if the first seems solid, then compare them.
His own best design decision — the API for the Tk toolkit — was his second idea, produced by deliberately discarding his first instinct during a long airplane ride.
The time cost is small (roughly 1–2% of total project time for high-level design) and pays for itself many times over in better outcomes.
This practice parallels the “alternatives considered” sections that companies like Uber added to design docs, which improved both the quality of designs and the quality of discussions.
Two general approaches to complexity in design
Eliminate complexity: the most powerful approach — redesign so special cases and edge conditions simply cannot occur.
Hide complexity through modularity: when complexity cannot be eliminated, encapsulate it behind clean interfaces so that most developers never need to think about it.
Good design uses both strategies in combination.
Error handling: define errors out of existence when possible
Error handling is one of the largest sources of complexity in software systems.
Ousterhout’s key principle: having more exceptions is not better — every exception a module throws imposes complexity on its callers.
Where possible, redesign so that certain errors cannot happen (e.g., by changing data structures or invariants), rather than adding checks and exception handlers.
He warns this principle is easily misapplied: students sometimes take it as permission to ignore errors entirely, which is dangerous. It should be used like a spice — in small, careful amounts.
A practical tip: when designing a module’s error interface, think from the caller’s perspective — if callers will handle ten different exceptions in only two distinct ways, collapse them into two.
Empathy as a design skill
One of the most important attributes of a great designer is the ability to shift perspective: think about a module from the caller’s point of view, deliberately ignoring knowledge of the internals.
This ability to empathize with users and fellow developers has value far beyond engineering — it’s a social skill that helps resolve miscommunication and conflict, which Ousterhout and the host agree are the hardest parts of software engineering.
Design reviews and the power of whiteboards
Ousterhout is a strong advocate for design reviews — getting multiple minds to think about trade-offs before implementation begins.
He loves in-person whiteboard sessions and has developed a specific technique for resolving contentious design debates:
List all arguments for and against a decision on a whiteboard.
Anyone can add an argument; no one can remove or dismiss someone else’s argument.
No argument can be repeated.
When no new arguments remain, take a weighted straw poll.
This process almost always reveals strong consensus, even when participants believed they were in total disagreement.
It works best in environments where people are smart, reasonable, and goal-aligned (e.g., startups or engineering teams) — not in adversarial political settings.
Design upfront vs. prototyping: neither extreme works
Ousterhout rejects both “design everything upfront” (waterfall) and “just start coding and let design emerge.”
His view: design permeates the entire process — upfront, during coding, during testing, during bug fixes.
Always do some design upfront to have hypotheses to work from, but be prepared to revise as soon as you discover problems — because software systems are too complex to fully predict the consequences of design decisions.
The only case for coding without design is when you’re too inexperienced to know how to design at all — in which case writing code is a learning exercise, and you should still be prepared to redesign.
Why software design is rarely taught
Before Ousterhout created his Stanford course, he believes there was no course anywhere in the world where software design (as opposed to tools or processes) was the primary subject.
He was motivated by the realization that the skills universities do teach — coding, algorithms, syntax — are precisely the ones most likely to be automated by AI, while the skill that matters most (design) is ignored.
How Ousterhout teaches software design at Stanford
The course is modeled on English writing classes: students write code, receive extensive feedback, then rewrite — the revision process is where the deepest learning happens.
Students build the Raft consensus protocol from scratch in two phases, receiving 50–100 detailed comments from Ousterhout on every line of code, plus peer reviews from classmates.
The second iteration is dramatically better — students experience firsthand the power of redesign and develop a new way of thinking about software.
Nine teams all solve the same problem, so students can compare approaches across teams — a form of learning that is rare in industry, where every project is a one-off.
Ousterhout warns students that when they enter industry, they will encounter senior engineers who lack these design skills, and discusses how to navigate that reality.
Disagreements with Robert Martin (Uncle Bob) on Clean Code
Short methods / single responsibility principle: Ousterhout agrees very long methods are problematic, but argues that Clean Code takes shortness to a harmful extreme. Relentlessly splitting code into tiny methods creates more interfaces, more entanglement, and more overall complexity. His concept of depth (maximizing functionality per unit of interface complexity) is a better guide than raw method length. He draws a parallel to microservices: Uber’s experience showed that thousands of tiny services created more complexity than they solved, and the company consolidated into mid-size, domain-aligned services.
Test-driven development (TDD): Ousterhout values unit tests and writes them for everything, but opposes TDD as a design methodology. He argues TDD encourages an extremely tactical style — solve one test at a time — with no point in the process where you step back and think about the overall architecture. He believes development should be organized around abstractions, not individual tests. The one exception: when fixing a bug, write the test that reproduces it first.
Comments: Ousterhout strongly disagrees with the Clean Code position that code should speak for itself and comments should be minimized. He argues there is critical information that cannot be expressed in code — assumptions, invariants, reasons for non-obvious decisions — and that comments are essential at interfaces and for class member variables. He notes that AI tools may partially compensate for missing comments (he uses ChatGPT to navigate the under-commented Linux kernel), but this is a poor substitute for well-documented code.
Current project: upstreaming a new transport protocol into the Linux kernel
Ousterhout is implementing Homa, a new data center transport protocol developed by his PhD student Ben Montazeri, which is 10–100x faster than TCP for many workloads.
He is upstreaming it into the Linux kernel — his first contribution to the kernel — and describes the review process as time-consuming but reasonable and high-quality, with feedback that has genuinely improved the design.
Reflections on the book and what would change
A Philosophy of Software Design was published in 2018; a second edition has already incorporated Ousterhout’s evolved thinking, with the biggest addition being a stronger emphasis on generality and eliminating specialization as a core design principle.
If rewriting from scratch, he would handle the error handling chapter more carefully to prevent misinterpretation.
He had hoped the book would generate constructive criticism and new ideas from the community, but this has happened less than he expected — possibly because the kind of comparative, repeated-observation learning his course enables is difficult to replicate in industry.
Closing recommendations
Ousterhout’s homepage links to a short page with book recommendations for software design, which will be linked in the show notes.
He welcomes constructive criticism and ideas from readers, and his broader goal is to raise awareness and consciousness about software design across the developer community.