Marc Morel · Programs · 9 min read

Trap 6 - The perfection

Engineers want to build great programs, but ego disguised as ambition sinks them. Ten faces of the perfection trap, and how to stay ambitious without gambling.

Perfection is an unconscious target we try to reach by building the most sophisticated solution possible. The difference between ambition and ego is that ambition serves the program and the users, while ego serves the engineer.

Boring won

Here is a story I love telling.

One of Europe’s most successful health-tech companies, the platform millions of people use to book a doctor, runs on what engineers politely call boring technology. Doctolib’s core is a Ruby on Rails monolith on PostgreSQL. By 2025 it had grown into one of the largest Rails monoliths in Europe, with over 3 million lines of code and 400+ engineers working in it.

No exotic language. No hundred microservices on day one. No framework invented in-house. Their job posts said it plainly: technology should serve the product.

That company scaled to a whole continent. Meanwhile, I have watched programs with far more sophisticated architectures never reach production at all.

That gap is what this episode is about.

Ambition or ego?

Let me say this first, because it matters: the people who fall into this trap are rarely lazy or careless. Quite the opposite. They are often the best engineers and architects in the room. They want to do great work, learn, and build something they will be proud of.

That is ambition, and programs need it badly.

The trap opens when ambition quietly turns into ego. Ambition asks: what does this program need to succeed? Ego asks: what would make this the most impressive thing I have ever built? From the outside, both look like high standards. Both come with brilliant slides. But only one of them is about the users.

That is the perfection trap: confusing the best solution with the most sophisticated one.

The ten faces of the trap

I have seen it take ten recurring shapes, in three families: what we build, how we build it, and how we ship it. Like the traps themselves, no program shows all ten. Most show three or four.

What we build

  • Too abstracted. A generic engine to handle every future case. Rules stored in metadata, configuration on top of configuration. It promises flexibility. In practice, the simplest change now needs the one person who understands the framework.
  • Over-engineered. Event sourcing for a back-office form. Twenty microservices for a product with three hundred users. A service mesh before the first customer. Each choice is defensible on its own; together they multiply the moving parts the team must master.
  • Built for the dream scale. Architecture sized for a hundred million users when the business plan targets one hundred thousand. Sharding, multi-region and caching layers that cost months now, for a load that may never come.
  • Gold-plated everywhere. Five nines, zero downtime and the strictest security profile demanded for every component, including the internal reporting tool. The bar is set by the most critical system, never by the actual risk.
  • Not invented here. An in-house framework, a homemade workflow engine, a custom identity server. Building it is exciting. Maintaining it for ten years, with the people who built it long gone, is not.

How we build it

  • Unknown or fashionable stacks. The new language or framework is exciting, often fresh from a conference, and nobody on the team has ever run it in production. Learning happens on the critical path, with the deadline watching.
  • The grand rewrite. The legacy system works, but it is ugly, so the program will replace it with the perfect one, all at once. Fred Brooks named this the second-system effect: the successor carries every idea the first one had to leave out. Meanwhile, the old system keeps changing, and the target keeps moving.

How we ship it

  • Never good enough to ship. The architecture phase is extended once, then twice. Nothing reaches production until everything is right, so nothing is learned from real users until it is far too late to change course.
  • Blind to production. Designs validated on a laptop and in slides, never under real load, real data volumes, real network failures or a real 3 a.m. incident. Production behavior is discovered at go-live, when it is most expensive to learn.
  • Misunderstood and misused. A powerful tool used against its grain: a cache treated as a database, a message broker used as a workflow engine, an ORM fighting the data model. The technology isn’t wrong. The way it is used is.

Delays first, then the minefield

The first price is time, and it is paid quietly. Every unfamiliar layer adds learning curves, integration surprises and debugging sessions nobody planned for. Sprint after sprint, the schedule slides. Everyone is working hard; the plan simply assumed a mastery the team didn’t have yet.

The second price is far worse, and it is why this trap deserves its own episode. Sometimes the program doesn’t just slip. It blows up.

Think of the architecture as a field you must cross. Every unknown technology, every untested behavior, every clever layer is a buried mine. One mine, you can map and defuse. Five or six, laid across the same path, and you are no longer engineering. You are gambling. The failure modes are the ones that make headlines: a go-live that can’t hold the load, a data migration that corrupts records, an outage nobody knows how to diagnose because the system is too clever to reason about under pressure.

The cruel part: each mine was laid with good intentions.

Early signals

You can usually see the trap coming months before it bites. Listen for these:

  • “We’ll need it later.” Flexibility built for requirements nobody has written down.
  • “We’ll build our own, it’s not that hard.”
  • “The rewrite will fix everything.”
  • The architecture diagram is harder to explain than the business problem.
  • Only one or two people can change a core component, and they are always busy.
  • The tech choices appear on the team’s CVs before they appear in production.
  • The design phase has been extended, and still nothing runs in production.
  • No load test, no failure test, no rollback rehearsal is planned before go-live.
  • New joiners need weeks to ship their first change.
  • “Simple” becomes a mildly insulting word in design reviews.

How to stay ambitious without gambling

The answer is not to ban innovation. A team forbidden to learn anything new disengages, and the best people leave. The answer is to put ambition where it pays, and keep everything else boring on purpose.

  1. Spend a small innovation budget, deliberately. Dan McKinley’s Choose Boring Technology gives every team a handful of “innovation tokens”. Spend them only where they create a real advantage for your users. Everywhere else, pick what the team already knows how to run at 3 a.m.
  2. One unknown per critical path. A new language or a new database or a new architecture style. Never all three on the path to the same go-live.
  3. Buy or adopt what doesn’t set you apart. Build only what makes your product different. Identity, workflow, messaging and frameworks are solved problems; let someone else carry them for ten years.
  4. Match the bar to the risk. Classify components by criticality, and set availability, security and performance targets per class. State of the art where the risk demands it, good enough everywhere else.
  5. Prove it before you bet on it. Any new technology earns its place through a spike under production-like conditions: real volumes, real failures, a real rollback. If it can’t be tested that way, it isn’t ready to carry the program.
  6. Ship a thin slice early. A walking skeleton in production within weeks: one real flow, end to end, on the real infrastructure. Then grow it. For legacy, replace piece by piece around the old system, the strangler pattern, rather than in one big bang.
  7. Make simplicity the ambition. The hardest engineering is building something a new joiner understands in a week and that still holds at ten times the load. Celebrate the design that removed a component, not the one that added three.
  8. Point ambition at the business problem. Doctolib’s engineers had plenty of hard problems to solve. They just weren’t in the framework. Scale, reliability, performance and a product people love are where great engineers make their name.
  9. Give curiosity a safe playground. Internal tools, non-critical components, hack days, proofs of concept with an explicit end date. People keep learning, and the mines stay outside the critical path.

Done well, this doesn’t feel like a restriction. It feels like focus. Teams stay motivated because their ambition finally lands somewhere it shows.

What to avoid

  • Building for the scale of your dreams instead of the next order of magnitude.
  • Generic engines when you have one concrete use case.
  • The same bar for every component, whatever its risk.
  • Building in-house what you could buy or adopt, when it doesn’t set you apart.
  • Several new technologies landing on the same go-live.
  • Choosing a stack nobody on the team has operated in production, without time to learn it.
  • A big-bang replacement of a system that works.
  • Months of design with nothing running in production.
  • Discovering production behavior at go-live instead of in rehearsal.
  • Letting the most senior voice settle a tech choice without evidence.

The AI twist

AI makes this trap cheaper to fall into. An assistant will happily generate a generic framework, an extra abstraction layer or a stack nobody on the team knows, in minutes and with confident comments. Writing complexity has never been faster. Understanding it, operating it and debugging it at 3 a.m. still takes exactly as long as before.

There is a second effect. Coding agents work best on code that is explicit, consistent and close to well-known patterns. Boring code is easier for people to reason about, and easier for AI too.

Boring is a choice, not a lack of ambition

The teams I admire most are not the ones with the cleverest architecture. They are the ones that chose the few places where they would be bold, and were humble everywhere else. Their ambition was for the product and the people using it, not for the diagram.

So next time a design review gets exciting, ask one gentle question: is this what the program needs, or what we would love to build? There is no shame in the answer. Just in not asking.

Have you seen a program sink under its own sophistication, or one that won by being boring on purpose? I would genuinely love to hear your story.

All situations described here are composites, drawn from many programs over 25 years.

Share:
Back