DEV Community

cartmann68
cartmann68

Posted on

The cost of simplicity

The simplest solution is usually best only while the problem itself is simple.

This simple statement is often overlooked in software engineering. Many developers are drawn to the idea of simplicity, but they fail to recognize that simplicity is not an absolute quality. It is relative to the complexity of the problem being solved.The recurring argument that sounds sensible at first is — why complicate things, is only valid when the problem itself is genuinely simple. When the problem is complex, refusing to acknowledge that complexity does not make it disappear. It merely forces that complexity to reappear somewhere else, often in a much uglier form.

Why use TypeScript and React when simple HTML and JavaScript already works? Why use Next.js when plain direct DOM APIs are enough? In operating system design, the same question could be asked about virtual memory and page tables. Why introduce all the machinery of address translation, TLBs, and page faults when the CPU could simply access physical RAM directly?

That instinct is not foolish in and of itself. In fact, it is often correct. If the problem is genuinely small, the simplest solution is usually the best one. A tiny static website does not need a large application framework. A small embedded system running one fixed program may not need virtual memory. A shed does not need the structural systems of a skyscraper. The mistake begins when we take something that works beautifully at small scale and assume that it must therefore remain the best solution at large scale.

Imagine a man who has built a wooden shed in his backyard. He needed some lumber, nails, a roof, a door, and perhaps a window. The whole thing could fit on a simple sketch. There are no elevators, no fire-suppression systems, no reinforced concrete core, no emergency stairwells, no backup generators, no ventilation shafts, no structural calculations for wind loads hundreds of feet above the ground. Now imagine that same man standing beside his shed and pointing toward a skyscraper in the distance. He shakes his head and says, “Look at all that unnecessary complexity. Pumps, elevators, steel reinforcement, control systems, fire doors, electrical rooms, emergency exits, drainage, backup power. I built my shed without any of that.” Everything he says about his shed is true. His conclusion is still absurd.

The skyscraper is not complicated because its engineers enjoy complication. It is complicated because eighty floors of people create problems that a shed never has to solve. The requirements themselves have changed. Once hundreds or thousands of people must safely live or work inside the same structure, entirely new problems appear: vertical transportation, fire evacuation, water pressure, structural movement, power distribution, ventilation, sewage, communication, and redundancy. The complexity did not originate in the architect's imagination — it came from reality.

Software works the same way. A simple website may be created with little abstraction and very few dependencies. But a serious, large-scale enterprise web application requires much more. It has to handle authentication, responsive data loading, caching, error handling, client-side routing, internationalization, smooth transitions and animations, modern UI/UX practices, loading and error states without a full page refresh, route middleware, granular access controls, complex business logic and a host of other concerns. The requirements themselves have changed. The complexity came from the client's requirements.

While a simple embedded system might be fine with direct memory access and a flat RAM structure. The simplest possible memory model is wonderfully direct. The CPU has an address, and that address refers to a physical location in RAM. If the processor requests address 42,000, the machine reads the byte stored at physical address 42,000. For a tiny computer running one fixed program, this is elegant. There is almost nothing between the program and the hardware. However, a serious operating system has to manage hundreds of processes running at the same time, protect their memory from each other, allow programs to grow in memory dynamically, two programs may want to share the same library, files should be mapped directly into memory, you want to give programs contiguous memory, manage memory fragmentation, consider performance, security and deal with hardware quirks all at the same time. At this scale a direct memory model is no longer optima and would introduce more complexity than using a virtual memory model with page tables, TLBs and address translation. Again, the complexity has come from the requirements of the system. Paging did not invent the need for isolation, sharing, dynamic growth, fragmented physical memory, or memory-mapped files. Those problems existed because modern computers were expected to do more. Paging gathered many of those problems into one coherent abstraction.

Rocket science is not complicated because rocket scientists want to be clever. It is complicated because the requirements of spaceflight are complicated. A rocket must survive extreme acceleration, vibration, and temperature changes. It must carry enough fuel to reach orbit while being light enough to lift off the ground. It must be aerodynamically stable while being structurally sound. It must be able to communicate with ground control while protecting its electronics from radiation. The complexity has come from the physics itself.

This is a recurring pattern in engineering: a mechanism may become more complicated locally in order to make the entire system simpler globally. The important question is whether the primitive tool still produces the simplest overall system once the problem is no longer primitive. If the requirements themselves are complicated, refusing architecture does not make the complexity disappear. It merely forces that complexity to reappear somewhere else, usually in a much uglier form: duplicated code, global state, special cases, scattered event handlers, undocumented conventions, fragile assumptions, and enormous chains of conditionals.

Much of civilization itself is built this way. We live on top of layers of systems that allow us to interact with enormous complexity through simple interfaces. At small scale, informal systems are often superior because formal structure would cost more than it provides. At large scale, the absence of structure becomes dangerous. Genuine complexity has to live somewhere. If the requirements are complicated, we cannot remove that complexity simply by refusing to acknowledge it. We can place it in abstractions, frameworks, type systems, schemas, hardware, documentation, institutions, conventions, or procedures. Or we can leave it scattered across people's memories, hidden assumptions, duplicated logic, manual work, and special cases. But it will live somewhere. Rejecting visible complexity often creates invisible complexity, and invisible complexity is usually more dangerous because nobody can clearly see where the rules are. The goal of engineering, then, is not simply to “make everything simple.” That advice is too shallow. Nor is the goal to use the most sophisticated tools available. The goal is to build the least complicated system that can reliably contain the actual complexity of the problem.

Top comments (0)