There’s a debate that comes up in almost every engineering organization I’ve worked with, usually triggered by one of two things: a system that’s gotten too big to change safely, or a team that just came back from a conference.
The question is always some version of: should we break this apart into microservices?
And the honest answer, more often than people want to hear, is: not yet. Maybe not ever. But there’s probably something in between that would solve the actual problem.
How we got here:
Monolith vs. Microservices — When Something In Between Is BetterMicroservices became the default prescription for scaling engineering organizations somewhere around 2015, and the logic made sense. Netflix, Amazon, and Google had broken their monoliths apart and moved faster because of it. If it worked for them, it should work for everyone.
What got lost in translation is context. Netflix was operating at a scale where independent deployability wasn’t a nice-to-have — it was existential. They had hundreds of engineers stepping on each other, deployment pipelines that took days, and a failure in one part of the system that could cascade across the entire product. Microservices solved real problems at that scale.
Most companies aren’t Netflix. Most companies have a system that’s grown messy over time, a team of twenty to fifty engineers who are frustrated by slow deployments and tangled dependencies, and a leadership team that’s convinced the answer is a full architectural overhaul. That is almost never the right move.
What the monolith gets wrong — and right
A monolith isn’t inherently bad architecture. A monolith that nobody paid attention to for five years is bad architecture. There’s a difference.
The things that actually make a monolith painful — slow deployments, poor test coverage, tightly coupled modules that break each other unexpectedly, no clear ownership boundaries — are problems of discipline, not problems of architectural style. Splitting a messy monolith into microservices doesn’t fix any of those things. It distributes them across a network, adds latency, introduces distributed systems complexity, and makes debugging dramatically harder. You’ve traded one set of problems for a worse set of problems, and you’ve done it at significant engineering cost.
A well-maintained monolith, on the other hand, is fast to develop in, easy to test end-to-end, simple to deploy, and trivial to reason about. For a team under fifty engineers, it’s often the best architecture available.
The modular monolith as a third option
Here’s what I’ve found works in practice for growth-stage companies: a monolith with intentional internal structure. Clear module boundaries. Strong ownership. Enforced separation of concerns at the code level, even if there’s no network boundary between components.
Think of it as getting the organizational benefits of microservices — clear ownership, defined interfaces, independent iterability — without the operational complexity. A payments module that only exposes a clean API to the rest of the system, even inside the same deployment boundary, behaves like a service. It can be extracted later if scale demands it. More importantly, it can be understood, tested, and changed now without spinning up a distributed systems team.
This is what “modular monolith” means in practice. It’s not a compromise — it’s a deliberate architectural choice that acknowledges where the company actually is and what the team can actually maintain.
When microservices are the right answer
This isn’t an argument against microservices. There are real scenarios where splitting makes sense.
When different parts of the system have genuinely different scaling requirements — a high-volume transaction processing engine and a reporting dashboard, for example — independent deployability and scaling start to matter. When teams have grown large enough that a single deployment pipeline becomes a bottleneck for multiple squads operating independently. When regulatory or compliance requirements create hard isolation boundaries between data domains. When you’re operating at a transaction volume where even a small percentage of latency improvement compounds into significant business value.
In those cases, extract services where the pain is — not as a wholesale architectural migration, but as a targeted investment in the specific parts of the system where the tradeoff makes sense.
The question to ask before you decide
The architectural decision should follow the organizational reality, not lead it. Before any conversation about microservices vs monolith, the real questions are: how many engineers do you have, how often are they stepping on each other, and what specific pain are you actually trying to solve?
If the answer is “deployments are slow” — that’s a CI/CD problem. Fix the pipeline.
If the answer is “we don’t know what anything does” — that’s a documentation and ownership problem. Fix the structure inside the monolith.
If the answer is “we have three hundred engineers and two of our services need to scale independently at 10x” — now we’re talking about microservices.
The architecture should fit the problem. The problem should be diagnosed honestly. And the diagnosis should happen before anyone starts drawing boxes on a whiteboard.