The curse of overengineering

Oct 2025

I think we all crave truths—methods or principles that always work—that promise that if we follow them, we’ll build good software.

Maybe we hear at a conference that splitting a monolith into microservices made a system “more scalable and maintainable.” Then we read that big tech runs everything on microservices. So we start to believe we should do the same.

It’s comforting to believe in these kinds of rules. They make ambiguous decisions feel simple. They let us stop thinking and start following.

And, maybe most importantly, they spare us from guilt, from those exhausting decisions where there’s no clear right answer and we just want to feel safe choosing something.

Unfortunately, there are no universal truths. Every system is different, every team is different, and every project has a different context.

What can we do?

The best we can do is critically evaluate every proposal we make. It might sound like a “best practice” to add abstractions around the code that uses the database so that, in the future, we can swap it out quickly and avoid vendor lock-in.

Then, if you think about it, what is the probability that this project will change the database provider? What happens if it never changes? What if it changes but it’s ten years later? Does the maintenance cost of the abstractions over ten years outweigh the cost of not having them?

I have come up with a checklist of questions to ask yourself when making these kinds of decisions. It’s like pilots on the runway going over the pre-flight checklist.

I’m going to call it the pre-engineering checklist, which we should go over before adding an extra layer, abstraction, framework, or feature:

  • It solves a real problem today, not a hypothetical one.
  • The problem is really painful (e.g., frequent blockers, high operational cost, or user complaints) and actually needs to be solved now.
  • The problem cannot be effectively solved with a simpler approach (e.g., configuration tweak, library function, or existing pattern/technology).
  • The benefits (e.g., faster development, better performance, easier scaling) clearly outweigh the added complexity & maintenance cost.
  • I have honestly considered my own biases and acknowledged the potential downsides and risks of this decision.

I’ll probably come back to this list often because I tend to overcomplicate things for the sake of learning a new technology, trying a new pattern, or just experimenting.

I hope it helps someone out there.