Skip to content

Design for Simplicity

Chapter Info

Calculating... Writing Progress: 60%

Simplicity is one of the hardest disciplines in system design. Complexity accumulates naturally as systems grow — through new requirements, dependencies, abstractions, exceptions, and decisions that each seem reasonable on their own. The challenge is to stay in control of it. Great systems are not simple by accident; they remain simple because complexity is questioned, contained, and deliberately placed.

alt text

What Is Simplicity? [What Is Simplicity] {1}

Simplicity is the discipline of making a system as easy to understand and reason about as the problem allows. It does not mean making the system small, basic, or less capable. It means removing what does not need to be there, making what remains clear, and keeping necessary complexity from spreading. A simple system contains the complexity required by the problem — and as little accidental complexity as possible.

alt text

Distill to the Essence [Distill]

“Simplicity isn’t just removing the superfluous. Simplicity is tackling the complexity of the world, and trying to distill something to the pure essence.” In system design, that means breaking complexity down until its essential elements and relationships become clear. Separate what is fundamental from what is incidental or superficial, remove what does not contribute to the outcome, and simplify the relationships that remain. The goal is not to deny complexity, but to understand it well enough to reveal the simplest structure that faithfully represents what matters.

image

Eric Kim

K.I.S.S. — Keep It Simple, Stupid.

“Simplicity isn’t just removing the superfluous. Simplicity is tackling the complexity of the world, and trying to distill something to the pure essence.”

Isolate What Can Change Independently [Isolation] {1}

Once complexity has been broken into its essential parts, isolate what can change, fail, or scale independently. Give each part a clear boundary and limit what it needs to know about the others. This keeps changes local, contains failures, and makes each part easier to understand. The same principle appears in microservices, modular monoliths, bounded contexts, and fault-isolation boundaries. Separate what does not need to move, change, or fail together.

alt text

What Can I Remove? [Remove] {1}

Simplicity requires removing what the system does not truly need. Every extra feature, option, abstraction, layer, dependency, exception, or special case adds something that must be understood, tested, and maintained. Ask of each one: does the value justify the complexity? If not, remove it. Accept the complexity the problem requires. Remove the complexity the solution does not.

alt text

Hide Complexity Behind Simplicity [Illusion of Simplicity] {1}

True simplicity does not require making all underlying complexity disappear. Some complexity is essential and must remain somewhere in the system. The architect’s role is to eliminate what is unnecessary, then contain and hide what remains behind clear interfaces and interactions. To the user, the system can feel simple even when substantial complexity exists underneath. The best designs do not expose complexity merely because they must contain it.

alt text

Grady Booch

“The task of the software development team is to engineer the illusion of simplicity.”

Too Simple Is Useless, Too Complex Is Unusable [Too Simple Too Complex] {1}

True simplicity requires navigating two dangerous extremes. Too simple becomes useless — hiding essential details or failing to solve the real problem. Too complex becomes unusable — covering every possibility at the cost of understanding and maintainability. The goal is to preserve the complexity the problem requires while removing the complexity it does not.

alt text

Hidden Complexity: The Two Ways to Build [Hidden Complexity] {1}

A simple design makes its structure and weaknesses easy to see. Complexity can do the opposite: layers, interactions, and special cases can hide problems without solving them. Prefer designs where problems are visible and easy to fix.

alt text

Tony Hoare

The Emperor’s Old Clothes (ACM Turing Award Lecture, 1980).

"There are two ways of constructing a software design: one way is to make it so simple that there are obviously no deficiencies, and the other way is to make it so complicated that there are no obvious deficiencies." — Tony Hoare

Benefits of Simplicity [Benefits] {1}

Simplicity Makes Systems Easier to Change [Easy Systems] {1}

Simple systems are easier to change because fewer dependencies, hidden assumptions, and special cases must move together. A local change is more likely to stay local, with fewer side effects and less coordination across the system. This reduces the cost and risk of evolving the software over time. Simplicity makes change smaller, safer, and easier to contain.

alt text

Robert C. Martin

Clean Architecture (2017).

Simplicity Makes Systems Easier to Understand [Understandable Systems] {1}

Simple systems make their structure and behavior easier to understand because relationships, responsibilities, and dependencies are easier to trace. Engineers spend less time reconstructing how the system fits together and more time working on the problem itself. Clarity also makes unusual decisions easier to notice and question instead of letting them disappear inside layers of complexity. A system is understandable when its structure helps explain itself.

alt text

Simplicity Reduces Risk [Reduces Risk] {1}

Every component, dependency, option, and special case creates another place where behavior can diverge from what we expect. Simpler systems have fewer interactions to reason about, fewer paths to test, and fewer hidden assumptions that can fail in production. They are also easier to diagnose when something does go wrong. Reducing unnecessary complexity reduces the surface area for mistakes, failures, and surprises.

alt text

Simplicity Matters Most When Things Break [Operational Simplicity] {1}

A system may look simple when everything works and become impossible to understand during a failure. Operational simplicity means making behavior, dependencies, failure modes, and recovery paths easy to see when the system is degraded. Clear signals, explicit flows, and predictable failure behavior reduce the amount of reasoning required under pressure. A system is not truly simple if operating it requires reconstructing its architecture during every incident.

alt text

Simplicity Preserves Future Options [Future Options] {1}

Simple systems are easier to evolve because they commit to fewer unnecessary assumptions, dependencies, and constraints. Every decision that hardens the architecture reduces the range of changes that remain easy later. By keeping the structure minimal and delaying commitments that are not yet necessary, simplicity preserves room to adapt as requirements, scale, and technology change. Simplicity today creates freedom tomorrow.

alt text

The Cost of Complexity [Cost of Complexity] {1}

As We Grow, Everything Becomes More Complex [Growth Complexity] {1}

As systems grow, they become harder to understand, coordinate, operate, and change. Complexity that accompanies growth is universal. A solo musician becomes an orchestra; a single application becomes a system of interacting components. Growth brings new relationships, dependencies, failure modes, and coordination costs. The challenge is not to prevent complexity from growing, but to prevent it from growing unnecessarily.

alt text

Complexity is Not Linear as Size Increases [Nonlinear Complexity]

As systems grow, complexity can grow faster than their size because each new component may introduce new dependencies, interactions, and failure modes. Adding one element does not necessarily add one unit of complexity — it can change the relationships among many existing elements. Scaling therefore requires more than adding capacity or components; it requires controlling the interactions created by growth.

alt text

Cognitive Load Is the Real Currency [Cognitive Load] {1}

Complexity does not primarily cost CPU cycles — it costs brain cycles. Human working memory is limited; every dependency, layer, exception, or unspoken convention that must be remembered increases cognitive load. As that load grows, engineers spend more effort reconstructing context and less effort solving the problem itself. The hidden price appears in slower onboarding, harder debugging, longer reviews, and more mistakes. Reduce what people must keep in their heads at once.

alt text

George A. Miller

“The Magical Number Seven, Plus or Minus Two” (Psychological Review, 1956).

Complexity Leaves Signals [Complexity Signals] {1}

Complexity is not always visible in a diagram. It appears in the work teams do every day: small changes touch many places, tests require extensive setup, failures have surprising effects, engineers avoid certain areas, and understanding a change requires reconstructing large parts of the system. Long onboarding, a long time to make a first safe change, and frequent reliance on a few experts are organizational signals of hidden complexity. Metrics such as coupling, dependency growth, change correlation, and test complexity can help expose these signals. When simple changes consistently require complex work, the system is telling you something.

alt text

Core Simplicity Principles [Core Principles] {1}

Abstraction: How Humans Handle Complexity [Abstraction — Mind] {1}

Abstraction is how humans survive complexity. Instead of handling every detail at once, we group, hide, and simplify — focusing only on what matters at a given level. In software, this means functions, modules, and APIs that let us reason about a system without holding all of it in our heads. As Fred Brooks noted, !!abstraction is not just a coding technique — it's a cognitive necessity!!. Without it, complexity overwhelms us.

alt text

Fred Brooks

The Mythical Man-Month (1975).

Simple Is Not the Same as Easy [Simple vs Easy] {1}

Simple and easy are not the same. Easy describes the effort required now; simple describes the structure of the result. A solution may be easy to build quickly yet create tangled dependencies that make every future change harder. Conversely, a simple design may require more thought upfront because responsibilities and relationships must be made clear. !!Optimize for structural simplicity, not immediate convenience.!!

alt text

Rich Hickey

Simple Made Easy (Strange Loop, 2011).

Prefer the Simplest Explanation That Works [Occam's Razor] {1}

When several designs solve the same problem, prefer the one that introduces the fewest unnecessary assumptions, concepts, components, and dependencies. This is Occam’s Razor applied to software design: !!do not multiply complexity without necessity.!! The simplest solution is not the one with the least code or effort, but the one that solves the problem while introducing the least additional structure.

alt text

Occam’s Razor

William of Ockham, 14th century.

Simplicity Comes from Clear Boundaries [Clear Boundaries] {1}

A system becomes easier to reason about when each component has a clear responsibility, strong cohesion, and minimal coupling to the rest of the system. When unrelated concerns are intertwined, understanding or changing one thing requires understanding many others. Good boundaries contain complexity: each part can be understood, tested, changed, and replaced with minimal knowledge of what surrounds it.

alt text

Design for the Next Reader [Next Reader] {1}

Software is read and changed far more often than it is first written. A simple design makes its intent, structure, and dependencies easy to discover. Engineers should not have to reconstruct the system from scattered details or hidden relationships before making a change. !!Design for the person who must understand the system six months from now — including yourself.!!

alt text

Essential Complexity vs Accidental Complexity [Essential vs Accidental] {1}

Not all complexity is equal. Essential complexity comes from the problem itself — the rules, constraints, scale, and capabilities the system genuinely requires. Accidental complexity comes from how we choose to solve it: poor abstractions, unclear boundaries, tangled dependencies, unnecessary layers, and systems grown without intention. The discipline of simplicity is not eliminating all complexity — it is eliminating accidental complexity while carefully managing the essential complexity that remains.

alt text

Place Complexity Deliberately [Place Complexity] {1}

Good architecture does not eliminate complexity; it eliminates unnecessary complexity and places what remains where it interferes the least. Every architectural decision simplifies something while introducing complexity somewhere else. The goal is not to make that complexity disappear, but to concentrate it behind clear boundaries where it can be understood, automated, and maintained. Complexity that must exist should have an intentional home.

alt text

Key Approaches for Reducing Complexity [Reducing Complexity] {1}

Architects rely on a small set of structural moves to keep complexity manageable: divide the system (Modularity), protect each part (Encapsulation), keep the parts loosely connected (Decoupling), and hide details behind useful concepts (Abstraction) — then make the resulting system intuitive for those who work with it (Developer Experience). Each addresses a different dimension of complexity; together they make complex systems easier to understand, change, and maintain.

Break the System into Modules [Modularity] {1}

Modularity breaks a complex system into smaller, coherent parts. Each module has a focused responsibility and a clear boundary, so engineers can reason about one part without understanding the entire system at once. Good modularity reduces the scope of what must be held in mind and makes the structure of the system easier to navigate. Break the system into parts that can be understood on their own.

alt text

Encapsulation for Clarity and Safety [Encapsulation]

Once a system is broken into modules, each module needs a clear boundary that protects its internals. Encapsulation keeps the internal workings of components hidden from the outside world. !!By exposing only what is necessary and hiding the rest, encapsulation prevents external code from interacting with internal state, reducing errors and unintended side effects.!! This clear separation between interface and implementation makes components easier to reason about and the codebase less error-prone.

alt text

Decoupling for Flexibility [Decoupling] {1}

Decoupling minimizes what components need to know about each other. Components should depend on clear contracts rather than internal details, assumptions, or implementation choices. Fewer and more explicit dependencies make each part easier to understand, replace, and evolve without coordinating unnecessary changes across the system. Depend on what a component promises, not on how it works.

alt text

Abstraction: Simplifying by Hiding Complexity [Abstraction — Technique] {1}

The same cognitive mechanism becomes a design technique in software. Abstraction creates simplified models and interfaces that expose what a system does while hiding how it does it. By working with these higher-level concepts, developers can design, understand, and modify a system without repeatedly navigating its full implementation complexity. Good abstractions reduce what must be understood at once without hiding what must be known.

alt text

Convention Over Configuration [Convention] {1}

Every configuration option is another decision someone must understand and make. !!Convention over configuration reduces that burden by providing sensible defaults for the common case!!, requiring explicit configuration only when a system needs to deviate from them. Good conventions make the simple path the default path — reducing setup, cognitive load, and opportunities for inconsistency while preserving flexibility where it is actually needed.

alt text

Standards Reduce Complexity [Standards] {1}

Standards reduce complexity by turning recurring decisions into shared answers. Without them, every team can choose its own tools, naming, interfaces, workflows, and practices — multiplying the number of patterns developers must learn and systems must support. A good standard captures a proven choice once and lets everyone reuse it without reconsidering the problem from scratch. Standards should not prevent justified exceptions, but they should make the common path predictable. !!Standardize what does not need to be different, so attention can stay on what actually does.!!

alt text

Enhancing Developer Experience (DX) [Developer Experience]

Once the structure is sound, the experience of using it matters too. Developer Experience simplifies software development by making interactions intuitive and straightforward. A well-designed DX reduces cognitive load, allowing developers to build, maintain, and extend systems with minimal friction. By prioritizing ease of use in tools, frameworks, and workflows, DX ensures that even complex systems can be developed efficiently, reducing onboarding time and support needs.

alt text

Practical Strategies for Simple Systems [Practical Strategies]

Good Naming Is the Foundation of Simplicity [Naming] {1}

Phil Karlton said there are only two hard things in computer science: cache invalidation and naming things. !!A good name is a free abstraction — it tells the reader what something is for without forcing them to read the implementation.!! A bad name is a tax every reader pays forever: each ambiguous identifier forces a small detour into context, multiplied by every future encounter. Simplicity at the code level starts here, before structure, before abstractions, before patterns. !!If you cannot name it clearly, you do not yet understand it clearly.!!

alt text

Phil Karlton

The aphorism about cache invalidation and naming is widely attributed to Karlton; no canonical primary publication is readily available.

Treat All Code Consistently [Code Consistency]

Code may serve very different purposes — application logic, infrastructure, automation, configuration, or tooling — but those differences should not create unnecessary differences in how we work with it. Treat code consistently: version it, review it, test it, document it, and deliver it through clear, repeatable practices. When every category of code invents its own conventions and workflows, developers must learn multiple ways of doing the same fundamental work. A unified approach reduces those variations, lowers cognitive load, and makes the entire system easier to understand and maintain.

alt text

Reuse Before You Build [Reuse Solutions]

Before building something new, look for a proven solution that already solves the problem well enough. Libraries, frameworks, shared internal components, and established tools can remove entire categories of design and maintenance work. Reuse is not about avoiding engineering; it is about spending engineering effort where it creates unique value. Solve common problems with proven solutions, and reserve custom work for what is truly specific to your system.

alt text

Transfer Complexity Instead of Owning It [Transfer Complexity] {1}

Simplicity does not always require eliminating complexity. Sometimes the best strategy is to move it outside the system your team must own. Managed services let a provider take responsibility for difficult infrastructure concerns such as provisioning, patching, scaling, replication, backups, and operations. !!The complexity still exists — but someone else specializes in managing it.!! Deliberately transferring complexity can be as valuable as reducing it.

alt text

Choose Boring Technology [Boring Technology] {1}

Every team has a finite budget for novelty — Dan McKinley calls it the !!"innovation token"!! budget. Spend three tokens on a new database, a new language, and a new orchestration platform, and you have none left for the actual product. Boring technology is boring because its failure modes are known, its operational quirks are documented, its hiring pool is large, and Stack Overflow has already answered tomorrow's question. !!Save your innovation tokens for the problem only your company has;!! everywhere else, prefer the tool that is one step short of cutting edge.

alt text

Dan McKinley

“Choose Boring Technology” (2015).

Collaborate Before You Reinvent [Collaborate]

Teams often face the same problems independently. Without visibility and collaboration, each team may build its own workaround, tool, or solution — duplicating effort and creating unnecessary fragmentation. !!A culture of collaboration makes shared problems visible and encourages teams to solve them together or reuse what already exists.!! Before building a new solution, look for existing work, share the need, and collaborate where possible. !!Solve common problems once, not independently in every team.!!

alt text

Simplicity Over Cleverness [Simplicity over Cleverness] {1}

Clever code flatters its author; simple code serves its team. The chained ternary, the metaprogramming trick, the one-liner that compresses ten lines — each may feel elegant when written but charges a comprehension cost every time it is read. Code is read and changed repeatedly throughout its life, so cleverness taxes every future reader. Simplicity is the harder discipline because it values being understood over appearing sophisticated. Boring code is a feature, not a failure — choose the version a tired teammate can understand at midnight.

alt text

Every Layer Must Earn Its Place [Every Layer]

Every layer has a cost. It adds something that must be understood, maintained, and navigated, and it increases the distance between a problem and its solution. Layers can be valuable when they hide complexity, create a useful boundary, or make the whole system easier to reason about. But a layer should never exist merely because another layer can be added. !!Keep only the layers whose value justifies the complexity they introduce.!!

alt text

Decisions Eliminate Complexity [Decisiveness]

Indecision often creates complexity because the system must keep multiple possibilities open at once. Instead of choosing one direction, we add options, configuration, abstractions, compatibility layers, and alternative paths so that the decision can be postponed. Some uncertainty is unavoidable, but preserving every possibility indefinitely makes the system harder to understand and change. When the evidence is sufficient, make the decision and remove the alternatives you no longer need.

alt text

Gregor Hohpe

“Excessive complexity is nature’s punishment for organizations that are unable to make decisions.”

Simplify Incrementally [Incremental Simplification] {1}

Existing systems rarely allow a clean restart. Simplification must usually happen while the system continues to serve users. Create clear boundaries around the area you want to improve, replace or simplify one part at a time, move behavior gradually, and remove the old path when it is no longer needed. Patterns such as the Strangler Fig apply this approach at larger scales. Do not wait for a rewrite to make the system simpler — make each change leave it simpler than before.

alt text

Challenge Requirements to Preserve Simplicity [Challenge Requirements]

Requirements shape complexity before architecture begins. A requirement that appears small can force additional components, special cases, integrations, or operational burden. Treat requirements as design inputs, not immutable constraints: understand the value behind them and challenge those whose cost in complexity exceeds the value they provide. Sometimes the simplest architecture comes not from finding a cleverer implementation, but from changing what the system is required to do.

alt text

Focus on the Vital Few [Vital Few] {1}

The Pareto principle suggests that a small part of a system often delivers most of its value, while the remaining edge cases, options, and refinements can account for a disproportionate share of its complexity. Simplicity means identifying the capabilities that matter most and designing them well before expanding into the long tail. The 80/20 split is not a mathematical rule, but a reminder: do not let the last 20% of value create 80% of the complexity.

alt text

Vilfredo Pareto and Joseph M. Juran

Pareto observed unequal wealth distribution in nineteenth-century Italy. Juran later named and popularized the broader heuristic as the Pareto principle, introducing the expression “vital few and useful many.”

When Complexity Is Worth It [Justified Complexity] {1}

Complexity Must Earn Its Place [Complexity Earns Its Place] {1}

Complexity is justified when it buys a capability the system genuinely needs — such as flexibility, performance, scalability, resilience, or extensibility. The question is not whether complexity is bad, but whether the value it creates is worth the cost of understanding, testing, operating, and maintaining it. Every added complexity should have a clear reason to exist.

alt text

Simplicity and Flexibility Pull in Opposite Directions [Simplicity and Flexibility] {1}

Simplicity reduces concepts, choices, and interactions; flexibility adds options and paths so a system can adapt to more situations. Both are valuable, but they naturally pull in opposite directions. Too little flexibility creates rigidity, while too much flexibility introduces complexity that may never be needed. Good design accepts enough complexity to provide the flexibility the system truly requires — and no more.

alt text

Finding the Balance [Finding the Balance]

Too little flexibility creates rigidity: every new need requires a fork, exception, or workaround. Too much flexibility creates complexity: every behavior becomes configurable, abstract, and harder to understand. The goal is not maximum flexibility, but the amount the system genuinely needs.

alt text

Premature Optimization [Premature Optimization] {1}

The most common way to overpay on complexity is premature optimization — building for scale you don't have, adding abstraction for use cases that don't exist, designing extension points for requirements that may never come. It feels responsible but is often speculation. !!Document potential bottlenecks, plan for them, but don't implement until they're real.!! Even a good optimization has a cost: it makes code harder to read and maintain. Build for today. Pay the complexity price when the need is real, not merely anticipated.

image

Wait for the Third Occurrence [Rule of Three] {1}

The mirror image of premature optimization is premature abstraction. The first time you write something, write it; the second time, tolerate the duplication; the third time, consider the abstraction. With too few examples, it is easy to guess the wrong axis of variation and create an abstraction that future cases must fight against. A wrong abstraction can cost more than the duplication it was meant to remove. Let repetition reveal the pattern before you encode it.

image

Simplicity in the Age of AI [Simplicity & AI] {1}

AI Can Handle More Complexity [AI Complexity] {1}

AI changes a fundamental constraint of software engineering: how much complexity can be navigated at once. AI can inspect large codebases, follow dependencies across many files, compare implementations, generate changes, and reason over more context than a human can comfortably hold in working memory. Tasks that once required simplifying a system just to make it understandable can increasingly be assisted by machines. AI raises the amount of complexity we can work with — and that raises a fundamental question: if AI can handle more complexity, does simplicity still matter?

alt text

Is Simplicity Still Relevant? [Is Simplicity Relevant] {1}

Yes — because navigating complexity is not the same as removing its consequences. Complex systems still contain more interactions, hidden assumptions, failure modes, and ways for a change to produce unintended effects. AI can help us understand and work through that complexity, but the complexity itself still exists in the system. AI raises the complexity we can handle; simplicity reduces the complexity we have to handle at all.

alt text

When Creation Is Cheap, Simplicity Becomes a Filter [Simplicity as Filter] {1}

AI makes creation extraordinarily cheap. We can generate more code, more text, more alternatives, more abstractions, and more solutions with almost no friction. The constraint is shifting from our ability to create to our ability to decide what deserves to remain. When creation becomes abundant, simplicity becomes a filter. Explore broadly with AI, then deliberately reduce, simplify, and distill until only what matters remains.

alt text

Keep Complexity Human-Understandable [Humans in the Loop] {1}

AI can understand, generate, and operate increasingly complex systems, but humans still need to make decisions, review changes, diagnose failures, and take responsibility for outcomes. Complexity should not grow beyond what the people responsible for the system can understand. Use AI to reduce the effort required to work with complexity — not as a reason to create systems that humans can no longer explain.

alt text

Simplicity Gives AI Better Context [AI Context] {1}

AI can navigate more complexity than a human, but it still depends on the structure and context it is given. Clear boundaries, consistent conventions, explicit interfaces, and predictable patterns reduce ambiguity and make intent easier to infer. In a tangled system, AI must reconstruct hidden relationships and guess which patterns are intentional; in a simple one, the architecture itself provides guidance. !!Simplicity makes the system easier not only for humans to understand, but for AI to reason about and change correctly.!!

alt text