Why Developers Keep Choosing Worse Technology

7 min read
programmingdesignphilosophy

Why Developers Keep Choosing Worse Technology

There is a pattern in software that drives developers crazy: the "worse" technology often wins.

By "worse," I do not mean useless. I mean tools that are a little uglier, a little less consistent, and a little more willing to leak complexity into your lap. The kind of technology where the happy path works fast, but the edge cases are your problem.

This post is about why that happens. Not as an excuse for sloppy engineering, but as a model you can use to predict what will spread, what will die, and what tradeoff you are actually making when you choose a stack.

We will start with a tiny design decision from Bell Labs that still affects your laptop today, then connect it to a broader rule: implementation simplicity and low adoption friction have a survival advantage, even when the design is not "the right thing."

The Ghost in wrapper.c

Open up the Git codebase. Navigate to wrapper.c and find the xread function.

Inside, you will find a loop that looks something like this:

ssize_t xread(int fd, void *buf, size_t len)
{
    ssize_t nr;
    while (1) {
        nr = read(fd, buf, len);
        if (nr < 0 && errno == EINTR)
            continue;
        return nr;
    }
}

This code is running on your machine right now. Every time you pull, commit, or check out a branch, this loop is spin-retrying because of a design decision made at Bell Labs in the early 1970s.

When Unix was being designed in Murray Hill, New Jersey, the engineers ran into a problem: what should happen if a system call, like reading from a slow device, is interrupted by a hardware signal?

If you were designing a theoretically "correct" operating system, the kernel would handle the interruption transparently. It would save the state, process the signal, and resume the read call exactly where it left off. The user-space program would never know it happened.

Unix did not do that.

Instead, when a blocking system call is interrupted by a signal, Unix may return -1 with errno set to EINTR (Interrupted System Call). It essentially tells the programmer: "Something happened. You decide whether to retry."

On modern Unix-like systems, the details vary. Some signals can be configured to automatically restart certain system calls, and some kinds of I/O (like regular file reads) often will not surface EINTR in practice. But the general shape of the interface remains: if you want robust I/O across environments, you write the retry loop.

This design decision is more than 50 years old. It forces every systems developer to write boilerplate wrapper loops around basic I/O operations. It is messy. It is inconvenient.

And it conquered the world.

EINTR System Call Handling Diagram

The Essay That Explained It All

In the early 1990s, Lisp pioneer Richard Gabriel published an essay titled "Lisp: Good News, Bad News, and How to Win Big." Embedded inside was a short section that has since become legendary: "The Rise of Worse Is Better."

MIT vs. New Jersey Locations

Gabriel contrasted two fundamental philosophies of software design:

The MIT Style ("The Right Thing")

  • Interface Simplicity: The user-facing interface must be simple and clean, even if the implementation becomes incredibly complex.
  • Correctness: Incorrectness is simply not allowed.
  • Consistency: The system must be totally consistent. If a feature causes inconsistency, it must be omitted.
  • Completeness: The design must cover all reasonably anticipated scenarios.

The New Jersey Style ("Worse Is Better")

  • Implementation Simplicity: The implementation must be simple, even if it means the interface is slightly uglier or forces complexity onto the developer.
  • Correctness: Correctness is highly valued, but implementation simplicity is more important than absolute correctness.
  • Consistency: Consistency is important, but it is better to sacrifice consistency for simplicity of implementation.
  • Completeness: The system does not need to cover everything. It is better to ship a 70% solution that is easy to implement and port.

To a computer science theorist, the MIT style is the obvious choice. We want clean interfaces, correct code, and elegant systems.

But Gabriel noticed a dark pattern: the New Jersey style has far better survival characteristics.

C++: The Great Virality

Consider the birth of C++.

In 1979, Bjarne Stroustrup was working at Bell Labs. He wanted to bring the modularity and object-oriented capabilities of Simula to the speed and efficiency of systems programming.

Stroustrup faced a classic choice:

  1. Build the Right Thing: Create a brand new, highly elegant, object-oriented systems language from scratch.
  2. Build the Simple Thing: Bolt object-oriented features directly onto C, inheriting all of C's quirks, syntax issues, and memory unsafety.

He chose the second option, initially naming it "C with Classes."

C++ was not elegant. Early C++ implementations translated C++ into C, leaning on existing C compilers and toolchains. But because it was compatible with C, it could run on machines that already had C compilers and C codebases.

Developers did not have to rewrite their existing libraries, learn an entirely new toolchain, or abandon their legacy systems. They just compiled their C code with the new compiler and gradually adopted classes.

By the time cleaner, more elegant languages came along, C++ had already established a massive ecosystem. It became the foundation of modern operating systems, browsers, game engines, and databases.

The ugly, compatible solution ran circles around the elegant, clean-slate alternatives.

The Adoption Gradient

If we formalize Gabriel's observations, we find a rule governing technology adoption:

The long-term survival of a technology is inversely proportional to the friction a newcomer faces when trying to get it running for the first time.

Beautiful, correct systems typically demand a high entry fee. They force you to understand their architecture, adopt their mental model, and set up complex environments.

Simple systems meet you exactly where you are. They are easy to install, easy to write, and easy to run, even if they are full of design compromises.

This is often visualized through the concept of a desire path:

A paved path is the "correct" solution. It is planned, concrete, and structurally sound. A dirt path is messy, muddy, and irregular. But people walk the dirt path because it is the shortest distance between where they are and where they want to go.

In software, developers are almost always in a hurry. They will choose the dirt path of implementation simplicity over the paved path of architectural purity.

The Ecological Reality

This phenomenon matches a well-known model in evolutionary biology: r/K selection theory, introduced by ecologists Robert MacArthur and E. O. Wilson.

  • r-strategists focus on producing a massive quantity of offspring with minimal investment in each. They adapt quickly to changing, unstable environments.
  • K-strategists focus on high-quality, heavily invested offspring. They thrive in stable environments but struggle when conditions shift rapidly.

r/K Selection in Software

Software ecosystems are highly unstable, fast-moving environments. They favor r-strategy, at least as a metaphor.

A language or tool that is easy to implement, compile, and port can spread across the internet in a matter of months. It doesn't matter if it is full of quirks. Once it achieves critical mass, the community will write the wrapper functions, build the tooling, and fix the bugs.

The MIT style (K-strategy) spends years polishing the perfect compiler and proving mathematical correctness. By the time it is ready to ship, the r-selected alternative has already captured the market.

What This Means When Choosing Tech

If you want useful takeaways instead of philosophy, here are the patterns that show up again and again:

  • Optimize for time-to-first-success: If a newcomer cannot get a "hello world" running quickly, the tool bleeds adoption, even if it is architecturally superior.
  • Prefer migration paths over rewrites: Compatibility layers and incremental adoption beat clean-slate designs because they respect existing code, teams, and deadlines.
  • Keep the core small and boring: A simple implementation can spread across platforms fast, and the ecosystem can add polish later. A complex core is hard to port, hard to debug, and slow to evolve.
  • Be explicit about what you sacrifice: If you trade correctness for simplicity, put guardrails in place. Good defaults, clear failure modes, and sharp documentation reduce the long-term cost of that trade.
  • Ship the 70% that solves pain today: A smaller tool that solves a real problem now often outcompetes a perfect tool that arrives next year.

The uncomfortable point is that "better tech" is not just about correctness. It is also about distribution.

The Man Who Argued with Himself

The irony of "Worse Is Better" is that Richard Gabriel spent years trying to disprove his own theory.

In 1992, the Journal of Object-Oriented Programming published Gabriel's editorial "Is Worse Really Better?", which tries to reconcile the idea with the realities of building software that people actually use.

Years later, on December 4, 2000, he published "Worse Is Better Is Worse" under the pseudonym Nickieben Bourbaki (a play on the collective pseudonym Nicolas Bourbaki). In that piece, Bourbaki plays the role of a concerned colleague, arguing that the slogan can teach the wrong lesson if it is taken as permission to lower standards.

Despite his attempts to defend the "Right Thing," the industry kept validating the New Jersey style.

The Strategic Choice

Recognizing "Worse Is Better" is not an excuse to write sloppy, unmaintainable code. It is an acknowledgment of how systems behave under natural selection.

Unix was flawed. C was dangerous. C++ is notoriously complex. Git's user interface is famously counterintuitive.

Yet, these tools form the foundation of our entire digital infrastructure. They did not win because they were perfect; they won because they were simple enough to implement, fast enough to run, and easy enough to integrate.

When you are choosing how to build your next library, service, or API, you will face this choice. You can build the elegant, theoretically pure architecture that takes a year to design, or you can build the simpler, slightly uglier version that compiles on everything and ships next month.

The paved path is beautiful, but the grass will grow over it if nobody walks it. Sometimes, the dirt path is the only way forward.

Sources & References