The Pragmatic Engineer thumbnail

The Pragmatic Engineer

The Philosophy of Software Design – with John Ousterhout

The Future of Software Engineering: Insights from John Ousterhout on Design, AI, and Best Practices

The rapid advancement of AI and large language models (LLMs) is transforming many fields, and software engineering is no exception. To understand the deeper implications of these technologies and the enduring importance of software design, we spoke with John Ousterhout—a Stanford professor, author of A Philosophy of Software Design, and a seasoned industry veteran. John’s unique blend of academic rigor and industry experience offers valuable perspectives on how AI tools are reshaping coding, why software design is more critical than ever, and practical advice for developers at all levels.


From Industry to Academia and Back: A Unique Perspective

John’s career spans both worlds. After earning his PhD, he spent 14 years at Berkeley before moving into industry—working at Sun Microsystems and founding startups. Yet, his passion for teaching and creative freedom drew him back to academia at Stanford, where he has been teaching and coding for over 15 years.

This blend of experience informs his views on software engineering challenges and opportunities. He notes that while academia and startups share the excitement of building new software with small teams, industry introduces pressures like marketing spin and the need to survive financially, which can sometimes compromise honesty and design quality.


The Tactical Tornado vs. the 10x Engineer

John introduces the concept of the Tactical Tornado—a prolific coder who rapidly produces lots of code, often in a tactical, short-sighted way. While such engineers might be celebrated for speed, they typically leave behind messy code that others must clean up. This contrasts with the idealized 10x engineer, who creates clean, stable, and highly functional designs that scale well over time.

Interestingly, many managers mistake tactical tornadoes for 10x engineers because they see immediate output without appreciating the long-term technical debt created. This distinction is crucial as AI-assisted coding tools emerge, potentially turning every engineer into a tactical tornado at their fingertips.


The Long-Term Impact of AI on Software Engineering

AI tools are becoming remarkably adept at generating low-level code, effectively automating what John calls "autocomplete" but at a much higher quality. This shift will likely transform the role of software engineers, freeing them from mundane coding tasks and pushing them to focus more on software design—the high-level decomposition and architecture of systems.

However, John remains cautious about AI replacing higher-level design tasks anytime soon. Design requires deep understanding, foresight, and creativity—qualities current AI tools lack. This makes software design skills increasingly important, yet ironically, these skills are seldom taught in universities today.


What is Software Design?

John frames software design as a decomposition problem: breaking down a complex system into smaller, independently manageable modules. This principle of decomposition is fundamental to all of computer science and is key to managing complexity.

He distinguishes design from implementation and emphasizes that design is an ongoing process that permeates development, testing, and maintenance.


Design Practices: Top-Down, Bottom-Up, and Iterative Approaches

Design can start from the top down—breaking the system into components before implementation—or bottom up, building pieces and then integrating them. In reality, good design involves an iterative mix of both, repeatedly refining components and their relationships.

John highlights the importance of designing deep modules—modules with simple interfaces hiding significant internal complexity—which helps manage cognitive load and system complexity.


The Importance of Designing Twice

One powerful piece of advice from John’s book is to design things twice. Initial ideas are rarely the best. Encouraging yourself or your team to come up with alternative approaches leads to better solutions without significantly increasing design time. For example, John’s second design for the TK toolkit API became one of his best professional achievements.

This practice aligns well with industry efforts like Uber’s design docs and RFCs that document trade-offs and alternatives to foster better discussions and decisions.


Managing Complexity Through Error Handling

Error handling is a major source of complexity in software. John urges designers to reduce the number of exceptions and special cases exposed by modules, as each exception increases cognitive load for users.

He advocates designing systems so that some errors are defined out of existence—eliminated by design rather than handled as special cases. However, he cautions against ignoring errors, emphasizing the need to balance simplicity with robustness.


Empathy and Perspective in Software Design

A unique and often overlooked skill in software design is empathy—the ability to shift perspective between the implementer and the user of a module. Good designers think deeply about the user’s experience, hiding unnecessary complexity and exposing only what’s needed.

John argues this mindset is invaluable not only in engineering but also for improving communication and collaboration within teams.


The Role of Design Reviews and In-Person Collaboration

John is a strong proponent of design reviews—discussions where teams critique design documents or whiteboard sessions before implementation. He shares a practical technique for resolving design disagreements by listing all arguments for and against on a whiteboard, ensuring no argument is dismissed prematurely. This often leads to surprising consensus.

He also champions in-person meetings and whiteboard sessions for their spontaneity and collaborative power, though acknowledges remote work has changed the dynamics.


Balancing Upfront Design and Agile Development

John believes design is a continuous process that should start upfront but evolve throughout development. While upfront design is essential to avoid wasted effort, plans must be flexible to adapt to unforeseen challenges—especially in new or unfamiliar domains.

This balanced approach contrasts with extremes like pure waterfall or no-design prototyping and aligns more with iterative agile methodologies.


Teaching Software Design: Insights from Stanford

Recognizing a gap in formal education, John developed a Stanford course focused exclusively on software design. Inspired by English writing classes, the course emphasizes iterative development, extensive code reviews, and rewriting based on feedback.

Students work on challenging projects like the Raft consensus protocol, and through cycles of design, review, and revision, they develop a deeper understanding of complexity and design principles. Feedback from students has been overwhelmingly positive, demonstrating that design skills can indeed be taught effectively.


Reflections on Coding Practices: Comments, TDD, and Method Size

John offers nuanced views on several popular coding practices:

  • Comments: Contrary to some opinions that discourage comments, John argues they are crucial where code alone cannot convey intent, especially at interfaces and class members. He stresses comments should avoid redundancy and instead provide insight unavailable in code.

  • Test-Driven Development (TDD): While valuing unit tests highly, John is skeptical of TDD as a design tool. He believes it encourages overly tactical, incremental development rather than thoughtful, high-level design.

  • Method Size and Single Responsibility: He warns against the dogmatic pursuit of short methods and single responsibilities, which can lead to excessive fragmentation and complex interdependencies. Instead, he advocates balancing functionality and interface simplicity by sometimes combining related code into deeper modules.


John’s Current Project: Bringing a New Transport Protocol to the Linux Kernel

John is actively coding a Linux kernel implementation of Homa, a high-performance transport protocol developed by one of his PhD students. This project aims to dramatically outperform TCP in data centers. John shares his experience with the rigorous Linux kernel review process, noting that the feedback has been constructive and improved the design significantly.


Looking Ahead: The Future of Software Design

John does not foresee old ideas experiencing a renaissance but believes continuous improvement in design practices is ongoing. The recent surge in AI code generation tools underscores the urgency of teaching and mastering software design as a critical skill that AI cannot replace.

He encourages the developer community to engage critically with design concepts and share feedback to collectively raise the level of software quality.


Recommended Reading and Getting Involved

Besides John’s own A Philosophy of Software Design (now in its second edition with updates emphasizing general-purpose design), he recommends exploring additional resources available on his website.

He welcomes constructive criticism and dialogue, inviting developers and students alike to deepen their understanding and practice of design.


Final Thoughts

As AI tools accelerate code production, the craft of software design becomes the defining skill that separates robust, maintainable systems from fragile, short-lived codebases. John Ousterhout’s insights remind us that software design is a creative, iterative process requiring empathy, discipline, and thoughtful trade-offs.

For engineers seeking to stay ahead, investing time in design principles, iterative refinement, and collaborative reviews is more important than ever. And for educators, John’s pioneering Stanford course exemplifies how design can be taught effectively to prepare the next generation for a future where AI and human creativity work hand in hand.


If you found these insights valuable, consider reading John Ousterhout’s A Philosophy of Software Design and exploring the Pragmatic Engineer deep dives on software design and architecture.

← Back to The Pragmatic Engineer Blog