If you search “microservices vs monolith” you’ll find thousands of articles that read like architectural manifestos. Microservices fans will cite Netflix and Amazon. Monolith defenders will point to Shopify and Stack Overflow. Both sides have good examples. Neither tells you what your team should do.
The reason the debate drags on is that it’s being argued on the wrong axis. The question isn’t which pattern is better in the abstract. The question is which pattern your team can actually own, debug, deploy, and evolve over the next three years.
What a Monolith Actually Costs You
The monolith critique is usually about scale: one codebase, one deployment unit, you can’t scale individual components independently, and the whole thing slows down as the team grows.
These criticisms are real, but they arrive much later than most teams expect. The actual costs that hit early:
- Deployment coupling. A change in one module requires a full deploy. A bug anywhere can block everything.
- Test suite sprawl. As the codebase grows, the test suite slows. A 20-minute CI run becomes normal, and developers start skipping it.
- Merge conflicts. When multiple teams work in the same codebase, coordination overhead grows non-linearly.
- Technology lock-in. It’s hard to adopt new languages, frameworks, or runtimes when everything shares the same stack.
None of these are inevitable, and none of them require microservices to fix. They require better module boundaries, faster pipelines, and cleaner deployment practices - things you need with any architecture.
What Microservices Actually Cost You
The pitch for microservices - independent deployments, independent scaling, team autonomy - is real. The costs are also real and often underestimated:
- Distributed systems complexity. Every service boundary becomes a potential failure point. Network latency, partial failures, and timeout handling are now your problem at every interface.
- Operational overhead. You now have 15 services, each with its own deployment pipeline, health checks, logs, metrics, and alerts. That’s a platform engineering job, not a feature team job.
- Data consistency. Transactions that were trivial in a monolith (update two tables in one DB commit) become distributed coordination problems. Sagas, event sourcing, and eventual consistency add real complexity.
- Local development friction. Running 10 services locally is painful. Developers resort to mocking dependencies, which means they stop testing real behaviour.
- Debugging across services. A single user request that touches 6 services is significantly harder to trace and debug than the equivalent monolith call stack.
Teams that adopt microservices before they’re ready spend more time on infrastructure than on product. The architecture that was supposed to speed them up slows them down.
The Actual Decision Criteria
Here are the questions that matter:
- How many independent teams do you have? Microservices make most sense when you have multiple teams that need to deploy independently without coordinating. If you have one team, a monolith is almost always faster.
- What are your actual scaling bottlenecks? If your bottleneck is database connections or a specific compute-heavy operation, you can solve that without splitting the entire application.
- Do you have platform engineering capacity? Running microservices well requires investment in tooling, observability, and deployment infrastructure. If that investment isn’t made, microservices become a liability.
- How stable is your domain model? Splitting services before you understand your domain means you draw the boundaries wrong. Reorganising service boundaries later is expensive. A well-structured monolith is much easier to split correctly once the domain is understood.
The Modular Monolith Is Usually the Right Starting Point
The false binary is the problem. Most teams that “go microservices” early are actually trying to solve a code organisation problem, not a deployment or scaling problem. The right answer to a code organisation problem is a modular monolith: one deployable unit with strict internal module boundaries, clear APIs between modules, and no cross-module data access.
A modular monolith gives you:
- Fast local development and CI.
- Simple deployment and rollback.
- Real module boundaries that make future extraction trivial when the time is actually right.
- A codebase that multiple teams can work in without stepping on each other, if module ownership is respected.
When you actually hit the limits - team size, deployment coupling, genuine scaling requirements - you extract the modules that need independence. You now have real data on where the boundaries should be, because you built the system first.
When to Actually Split
Extract a service when:
- A specific component has radically different scaling requirements than the rest of the system.
- A team needs to deploy that component independently, on its own schedule, without coordinating with other teams.
- The component has a different operational profile (e.g., a batch processing job, a real-time stream processor, a model inference endpoint).
- You need to use a different technology stack for a specific capability.
Not because a conference talk said microservices are the modern way to build software.
The Architecture That Survives Is the One Teams Can Operate
The best architecture is the one your team ships features in, debugs confidently, and deploys without anxiety. That’s not a monolith or microservices answer. It’s a team, context, and maturity answer.
Start simple. Enforce clean boundaries from day one. Extract when extraction solves a real problem you’re already experiencing. That’s the approach that ages well.