Somewhere in the last decade, “we use microservices” turned into a status symbol — proof you were building something serious. That framing has quietly wrecked more architectures than it saved. Teams of eight split a working app into twenty services, then spend two years fighting network calls instead of shipping features.
The real monolith vs microservices decision isn’t about which one is modern. It’s about a single trade: you give up simplicity to buy independent deployment and team autonomy — and that trade only pays off past a certain size. Below it, you’ve bought a bill you can’t afford.
This post is part of the System Design Foundations series. By the end you’ll have a clear model of what each architecture actually is, the differences that matter in practice, and one deciding factor that cuts through the hype.
- Microservices scale teams, not traffic. A well-built monolith can serve enormous load; you reach for services when coordinating one codebase — not one server — becomes the bottleneck.
- The default in 2026 is a modular monolith: one deployable, clean internal boundaries, split into services only when a specific pain forces it.
- Start with the monolith. It’s cheaper, faster, and it teaches you where the real service boundaries are before you commit to them in code.
What a Monolith Actually Is#
A monolith
is one application that ships as a single unit. The user interface, the business logic, and the data-access layer all live in one codebase and run in one process. When you deploy, you deploy the whole thing.
That gets caricatured as a “big ball of mud,” but that’s a code-quality problem, not an architecture. A well-structured monolith can be clean, layered, and a pleasure to work in. Because everything runs in one process, there’s no network between components: a call from one module to another is a function call, not an HTTP round-trip. No serialization, no retries, no partial failures. You can wrap a whole request in one database transaction and trust it.
The upgrade most teams should reach for isn’t microservices — it’s a modular monolith
: the same single deployable, but organised into modules with boundaries the code actually enforces. You get the internal separation of services without paying for the network in between.
What Microservices Actually Buy You#
Microservices break that single application into small, independently deployable services that communicate over the network. Each service owns one business capability — and, crucially, usually its own database — so services stay decoupled. Martin Fowler’s microservices definition rests on exactly this: small services, independently deployable, organised around business capabilities.
Read that list of benefits again, because it’s specific. Independent deployment. Independent scaling. Team autonomy. Fault isolation. Notice what’s not on it: raw performance. A network hop is always slower than a function call. Microservices don’t make your system faster — they make it divisible, so different teams and different scaling needs stop colliding.
The cost is that you’ve turned local certainties into distributed problems. That in-process transaction is now a saga across three services. That function call can now time out. You need service discovery, request tracing, and a way to reason about a call that half-succeeded. Fowler is blunt about this in Microservice Trade-Offs: the boundaries buy you flexibility and cost you the ease of a single process.
Monolith vs Microservices: The Differences That Matter#
Skip the whiteboard theater. These are the differences you’ll actually feel in a sprint.
| Dimension | Monolith | Microservices |
|---|---|---|
| Deployment | One unit, one pipeline | Each service ships on its own |
| Communication | In-process function calls | Network calls (HTTP/gRPC/queue) |
| Data | One shared database, real transactions | A database per service, eventual consistency |
| Scaling | Scale the whole app | Scale each service independently |
| Failure | Process is up or down | Partial failure is the normal state |
| Team fit | One team, one codebase | Many teams, clear ownership |
| Operational cost | Low — one thing to run | High — orchestration, tracing, service mesh |
Read the table top to bottom and a pattern falls out. Every row where microservices win — independent deploys, independent scaling, team ownership — is about dividing work among people and load. Every row where they lose — communication, data, failure, cost — is the price of putting a network where a function call used to be. You’re not choosing the better architecture. You’re choosing which set of problems you’d rather have.
The Real Deciding Factor: Team Size, Not Traffic#
Here’s the reframe that settles most arguments. The question isn’t “will we get big?” — it’s “how many people need to change this code at once?”
A single monolith can serve enormous traffic. You scale it by running more copies behind a load balancer; the shared database is usually the real ceiling, and that ceiling is very high. Traffic almost never forces a split on its own.
What forces the split is people. When forty engineers share one deployment pipeline, one team’s risky change blocks everyone else’s release. Merge conflicts multiply. “Whose code broke the build” becomes a daily standup. Microservices exist to make that pain go away — to let each team own, deploy, and scale its piece without a meeting. Below roughly thirty engineers, that coordination overhead is cheap enough that a monolith wins on almost every axis. Above it, the calculus flips.
If you’re choosing microservices because of how much traffic you expect, you’re probably solving the wrong problem — a monolith plus more instances handles load. Choose microservices for how many teams need to ship independently.
When to Choose a Monolith#
Default here, and stay here longer than feels fashionable. A monolith is the right call when:
- Your team is small. Under ~30 engineers, one well-modularised codebase is faster to build and reason about than a fleet of services.
- You’re still finding the boundaries. Early on you don’t yet know where the seams belong. A monolith lets you learn them cheaply before you weld them into network contracts.
- Time-to-market matters most. No service contracts to negotiate, no infrastructure to stand up — you build features, not plumbing.
- Your data is tightly related. If most operations touch overlapping data, one database with real transactions beats stitching consistency back together across services.
When to Choose Microservices#
Reach for services when a real, named pressure justifies the operational bill:
- Teams are colliding in one codebase. Deploys block each other and ownership is blurry — the classic sign you’ve outgrown a single pipeline.
- Parts of the system scale very differently. Image processing needs GPUs and burst capacity; auth needs steady, cheap uptime. Splitting lets each scale on its own.
- You need fault isolation. One non-critical feature should never be able to take the whole system down.
- You have the operational muscle. Microservices assume real DevOps maturity — CI/CD, observability, tracing, on-call. Without it, you get distributed failures with none of the tooling to debug them.
Before you split a service out, try to write down the exact pain it removes — a blocked deploy, a scaling mismatch, a fault you must isolate. If you can’t name it in one sentence, you’re not ready to pay for the network.
The 2026 Default: Modular Monolith First#
The industry has quietly walked back “microservices by default.” After enough public postmortems from teams that decomposed too early — and after modern tooling made modular monoliths genuinely pleasant — the mature move is to start with one and split with intent.
Martin Fowler called this MonolithFirst years before it was consensus: almost every successful microservices story began as a monolith that got too big, and almost every system built as microservices from scratch ran into serious trouble. The reason is simple — you can’t design good service boundaries for a domain you don’t understand yet, and you only understand it by building it.
So the 2026 default is a modular monolith you could split later: clean internal modules, enforced boundaries, one deployment. When a specific pressure appears, extracting a module into a service is a known, well-tooled migration — not a rewrite. You get most of the discipline of services now, and you keep the option to split without paying for it until you must.
What Changes When You’re Building AI Systems#
If you came to backend architecture from the AI side — more comfortable with models and pipelines than with load balancers — this is the decision you’re most likely to get wrong, because the LLM tutorials skip it entirely. The good news is the rule still holds. The twist is that three things about AI systems bend it.
The pieces scale on completely different curves. A traditional app scales fairly evenly; an AI app does not. CPU-bound preprocessing, GPU-bound inference, and CPU-bound post-processing have almost nothing in common in cost or capacity — a GPU inference box is expensive and bursts hard, while the work around it is cheap and steady. Weld them into one deployable and you pay for idle GPUs every time you add capacity for traffic that never touches the model. This is one of the few places the “start as a monolith” default bends early: pulling the inference service out on its own often earns its keep sooner than it would in a plain CRUD app, because the scaling mismatch is that extreme.
AI-assisted coding quietly strengthens the monolith-first case. The classic argument for splitting early was that boundaries are expensive to move once code hardens around them. But if a coding agent can refactor a module boundary across a modular monolith in an afternoon, that cost drops — and “delay the split until you actually understand the domain” gets cheaper, not riskier. The more capable your tooling, the longer you can responsibly stay in one deployable and let the real seams reveal themselves before you weld them into network contracts.
And an agent isn’t a service at all. The moment part of your system is an AI agent, the monolith-versus-services framing stops covering it — an agent decides its own path at runtime and breaks the guarantees a normal service gives you. That’s a different decision, and I take it apart in AI Agents vs Microservices; the operational side of running those long, unpredictable calls is in Long-Running AI Workflows.
The AI-era rule of thumb: split the GPU inference service out early, because its scaling curve is nothing like the rest of the app — and keep everything you can write down as a plain service inside one modular deployable for as long as your tooling lets you move the boundaries cheaply.
Quick Recap#
- Monolith: one deployable, in-process calls, one database. Simple, cheap, fast to build.
- Microservices: independent services and databases over a network. Divisible, resilient, operationally expensive.
- The deciding factor is team size, not traffic — services solve people-coordination pain, not load.
- 2026 default: modular monolith first, split a service out only when a named pressure justifies it.
Conclusion#
The monolith vs microservices choice stops being hard the moment you stop asking which one is more advanced. Ask instead what you’re actually trading — simplicity for divisibility — and whether you’re big enough to need the divisible version yet. Most teams aren’t, for longer than they think. Build the modular monolith, learn where the real seams are, and extract services only when the pain is loud enough to name.
What actually pushed you off a monolith — was it team collisions, a scaling mismatch, or something the hype never warned you about? Tell me in the comments.
- AI Agents vs Microservices — why an agent isn’t just another service, and the four guarantees it quietly breaks.
- Long-Running AI Workflows — how to run slow, unpredictable AI calls without wedging your architecture.

