Every few years, a new framework arrives with the promise of solving complexity by introducing more abstraction. The pitch is always the same: adopt this tool, learn this pattern, and your codebase will become simpler, faster, more maintainable. I have spent two decades watching this cycle — first as a junior developer dazzled by novelty, then as a lead engineer cleaning up the wreckage.
The most productive codebases I have ever worked in had something in common. They were, by any fashionable measure, boring. They used proven patterns. Their folder structures were predictable. Their dependencies were few and well-understood. When a new developer joined the team, they could ship meaningful code within days, not weeks.1
This is not an argument against innovation. It is an argument against confusing novelty with progress, and against the particular form of self-deception that leads teams to rebuild working infrastructure for the thrill of using something new.
The Seduction of the New
There is a specific feeling that every developer knows. You read a blog post about a new pattern — say, event sourcing, or hexagonal architecture, or the latest state management library — and something clicks. You see how it solves a problem you have been wrestling with. You feel the dopamine hit of potential.
The trouble is that this feeling is indistinguishable from genuine insight. Your brain cannot tell the difference between “this solves my problem” and “this is interesting and I want to try it.” Both register as excitement. Both feel like discovery.2
I have fallen into this trap more times than I care to admit. In 2017, I rewrote a perfectly functional REST API using CQRS and event sourcing because I had just attended a conference talk that made it seem inevitable. The rewrite took four months. The original had taken three weeks. The rewrite introduced seventeen new failure modes.
What “Boring” Actually Means
When I say boring, I do not mean outdated or incompetent. I mean predictable. I mean code that does what you expect it to do, organized where you expect to find it, using patterns you have seen before. Boring code is code that has surrendered the ambition to be clever in favor of the ambition to be clear.
Consider the structure of a typical Node.js API service. There are really only a handful of things it needs to do: accept requests, validate input, execute business logic, persist state, and return responses. The number of reasonable ways to organize this is small. Yet I have seen teams invent baroque architectures that obscure these fundamentals behind layers of indirection.
A concrete example
Here is the route handler for a user creation endpoint in a boring codebase. It does nothing surprising. That is its virtue.
const createUser = async (req, res) => { const { name, email } = req.body; // Validate if (!name || !email) { return res.status(400).json({ error: 'Name and email are required.' }); } // Check for duplicates const existing = await db.users.findByEmail(email); if (existing) { return res.status(409).json({ error: 'A user with this email already exists.' }); } // Create const user = await db.users.create({ name, email }); return res.status(201).json({ user }); };
No command bus. No domain events. No mediator pattern. Just a function that reads top to bottom, does exactly what its name suggests, and handles its own errors. A junior developer can understand it in thirty seconds. A senior developer can modify it with confidence at three in the morning.
Compare this with the same endpoint in a codebase that has adopted every pattern from a domain-driven design textbook:3
// CreateUserCommandHandler.js class CreateUserCommandHandler { constructor(userRepo, eventBus, validator) { this.userRepo = userRepo; this.eventBus = eventBus; this.validator = validator; } async handle(command) { await this.validator.validate(command); const user = User.create(command.payload); await this.userRepo.save(user); await this.eventBus.publish( new UserCreatedEvent(user) ); return user; } }
This second version is not wrong. But it distributes a simple operation across at least four files, introduces three dependencies that must be wired together, and makes the actual behavior harder to trace. For a system with ten entities, this is manageable. For a startup with forty endpoints, it is a tar pit.
The best code I have written is the code I later realized I did not need to write at all. Every layer of abstraction is a bet that future complexity will justify present cost. Most of those bets lose.
Compound Returns
Boring architecture pays compound returns because its costs are front-loaded and its benefits accumulate over time. The decision to use a simple folder structure, to avoid unnecessary abstraction, to write explicit code instead of clever code — these choices feel expensive in week one and cheap in month twelve.
Here is what I have observed, consistently, across the boring codebases I have maintained:
- Onboarding time drops by half. New team members can navigate the codebase using intuition rather than tribal knowledge.
- Bug density decreases. When code does one thing in one place, there are fewer hiding spots for defects.
- Refactoring becomes tractable. Explicit code is easy to search, easy to understand, and easy to change.
- Dependency count stays low. Boring codebases resist the urge to add packages for marginal convenience.
The compounding effect is real. A codebase that is easy to understand today is easy to extend tomorrow. A codebase that is easy to extend attracts fewer workarounds. Fewer workarounds mean less technical debt. Less debt means more time for features. More features shipped with confidence mean a healthier team.4
The Discipline of Restraint
Choosing boring architecture requires a kind of discipline that the industry does not reward. Conference talks celebrate novel solutions. Blog posts showcase cutting-edge patterns. Performance reviews favor visible impact. Nobody gets promoted for saying, “I evaluated six approaches and chose the simplest one.”
Yet this restraint is precisely what separates senior engineers from experienced ones. Experience tells you what is possible. Seniority tells you what is wise. The gap between the two is measured in production incidents, late-night debugging sessions, and the slow erosion of team morale that comes from working in a codebase that nobody fully understands.
I have a personal rule that I apply to every architectural decision: if I cannot explain the why in a single sentence that does not reference the technology itself, I am probably solving the wrong problem. “We use event sourcing because it lets us reconstruct any historical state for audit compliance” passes this test. “We use event sourcing because it decouples our write model from our read model” does not — that describes what it does, not why your specific system needs it.
The Long Game
Software is a long game. The decisions you make today will constrain the decisions available to you in two years. A boring architecture keeps those future options open. An exciting architecture narrows them.
I think about this often when I look at the tools I reach for daily. Node.js. Express. PostgreSQL. Plain SQL queries. JSON. These are not glamorous choices. They are not the choices I would showcase at a conference. But they are the choices that let me sleep at night, because I know exactly how they will behave when something goes wrong.
The best architecture is the one your whole team can hold in their heads. The best code is the code that needs the fewest comments because the structure itself communicates intent. The best system is the one that works so reliably that you forget it exists.5
This is the compound return of boring architecture. Not the thrill of the new, but the quiet satisfaction of the durable. Not cleverness, but clarity. Not the system that impresses your peers at a meetup, but the system that is still running — unmodified, un-debugged, and unremarkable — three years from now.
Build for the engineer who will maintain your code at three in the morning, six months from now, with no context beyond what the code itself provides. That engineer might be you.A principle I return to often
I do not expect this essay to change anyone’s mind. The allure of the new is too strong, and the rewards of restraint are too quiet to compete with the dopamine of a fresh npm init. But if you are a developer who has been in this industry long enough to feel the weight of accumulated complexity, I hope you will find some validation here.
Build boring things. Build them well. Watch them compound.