← Software Engineering & Cloud Studio
Concept Explainer · Software Engineering

Monolith vs Microservices

The real dividing line isn't codebase size or file count — it's whether your components ship, scale, and fail together, or independently across a network.

Ask a team why they're "doing microservices" and a common answer is some version of "our monolith got too big." But size was never the actual problem — a million-line monolith with clean internal module boundaries can be perfectly maintainable, and a "microservice" with 200 lines can still be a nightmare if it's tightly coupled to three others. The real distinction is the deployment boundary: a monolith is one deployable unit where every module talks to every other module through in-process function calls, while microservices are multiple independently deployable units that talk to each other over the network. Everything else — scaling, blast radius, debugging difficulty, transaction complexity — falls out of that one architectural choice.

Monolith: one deployable unit

1 Process
ONE PROCESS · ONE DEPLOYABLE ARTIFACTOrders modulefunction callUsers modulefunction callInventory modulefunction callshared databasesingle deploy — the whole artifact
Module-to-module calls
In-process, ~nanoseconds
A function call, not a network hop — no serialization, no timeout to configure.
A bug in one module
Can take down everything
One unhandled exception or memory leak in Inventory can crash the whole process — Orders and Users go down with it.

Microservices: independently deployed, talking over the network

3 Processes
API GATEWAYOrders serviceown process · own deployorders DBUsers serviceown process · own deployusers DBInventory serviceown process · own deployinventory DBHTTPgRPCeach box ships on its own schedule — deploying Inventory never touches Orders or Usersdeploy #1deploy #2 (today)
Service-to-service calls
Network, ~milliseconds
Every call can fail, time out, or arrive out of order — the network is never free.
A crash in Inventory
Orders and Users keep running
Blast radius is contained to the failed service — though callers now need to handle it being unavailable.
Why this works

Every microservices tradeoff traces back to one thing: the network is now in the critical path.

In a monolith, "Orders" asking "Inventory" to check stock is a function call inside one process — it's fast, it can't partially fail, and if both modules touch the same database, wrapping the whole operation in a single ACID transaction is trivial: either everything commits, or everything rolls back. Split those into separate services with separate databases, and that same operation now crosses the network, which means it can time out, arrive twice, or succeed on one side and fail on the other — there is no single transaction spanning both databases anymore. That's why microservices architectures need patterns a monolith never had to think about: retries with idempotency keys, circuit breakers, distributed tracing to follow a request across process boundaries, and sagas or eventual-consistency patterns in place of a single ACID commit. In exchange, you get what a monolith can't offer: Inventory can be scaled to ten instances during a flash sale while Users stays at two, Inventory can be rewritten in a different language or redeployed hourly without touching Orders, and a memory leak in Inventory no longer takes the whole system down with it.

Common misconception
"Microservices are always more scalable, so they're just the better architecture."

No — microservices let you scale individual services independently, which is a genuinely different property from being "more scalable" overall. A well-built monolith running behind a load balancer, with a read-replica database and a cache in front of it, can comfortably serve enormous traffic — Shopify, Stack Overflow, and Basecamp have all run (or still run) core parts of their business on architectures a lot closer to a monolith than a microservices mesh, specifically to avoid the tax that comes with splitting up. That tax is real: every service boundary adds network latency, a new failure mode to handle, infrastructure to run (service discovery, per-service CI/CD, distributed tracing), and — the part teams underestimate most — the loss of a single ACID transaction across what used to be one database. A small team adopting microservices before they have a scaling or team-ownership problem that actually requires it is one of the most common expensive mistakes in system design: the decomposition doesn't remove complexity, it moves it from inside the codebase to across the network, and now you're paying operational costs for a scaling problem you didn't have yet. The right call depends on team size, deploy cadence, and which parts of the system actually have divergent scaling or ownership needs — not on which architecture sounds more modern.

Related Concept Explainers
Latency vs. Throughput
Read it →
SQL vs NoSQL
Read it →
Unit vs Integration vs E2E Testing
Read it →
Idempotent vs. Non-Idempotent Operations

Monolith vs Microservices — Concept Explainer

Explains the real architectural boundary between a monolith and microservices — independent deployability across a network, not codebase size — using side-by-side deployment diagrams, and works through the genuine tradeoffs: simpler transactions and debugging versus independent scaling and deployment, at the cost of network latency and distributed-transaction complexity.

Why This Is Commonly Confused

Teams often equate "monolith" with "big, messy codebase" and "microservices" with "clean, modern architecture" — but a monolith can be a well-organized modular codebase with strict internal boundaries (a "modular monolith"), and a microservices system can be a tangled mess of tightly-coupled services that all have to deploy together anyway, which defeats the entire point. The actual defining line is deployability: can this piece of the system ship, scale, and restart independently of the others, without coordinating a release with them? If yes, it is architecturally a separate service, regardless of how the code is organized internally. If no — even if it lives in its own repository — it is functioning as part of a monolith.

The Real Tradeoffs

A monolith keeps everything in one process: module-to-module calls are function calls (fast, can't partially fail), a single database can be wrapped in one ACID transaction (all-or-nothing correctness for free), and there is one thing to deploy, one thing to debug with a normal stack trace, and one thing to monitor. The cost is coupling: every module scales together even if only one is under load, and any deploy — even a one-line fix in an unrelated module — means redeploying and re-testing the whole artifact, and a severe bug in one module can crash the entire process.

Microservices flip that: each service scales, deploys, and can even use a different tech stack independently, and a crash in one service doesn't directly crash the others. The cost is that every one of those cross-service calls now goes over a network that can be slow, drop packets, or partially fail — so there is no free ACID transaction spanning two services' databases, and teams have to reach for patterns like sagas, idempotency keys, retries with backoff, circuit breakers, and distributed tracing to get back some of the reliability guarantees a monolith had by default.

Where This Matters in Practice

Most successful systems don't start as microservices — they start as a monolith (or modular monolith) and split out services only when a specific part of the system has genuinely divergent scaling needs, a different release cadence, or is owned by a separate team that needs to deploy without coordinating with everyone else. Splitting too early adds real operational cost — service discovery, per-service CI/CD pipelines, network reliability engineering — before there's a problem that justifies it. The System Architecture Planner and Docker Compose Visualizer tools in this studio let you sketch either shape and see the resulting deployment topology before committing to it.

Frequently asked questions

Is a "modular monolith" a real middle ground, or just marketing?

It's real. A modular monolith enforces the same clean internal boundaries between components that microservices have (each module owns its own data access, communicates through defined interfaces, no reaching into another module's internals) — but all modules still deploy and run as a single process. It gets most of the maintainability benefits people associate with microservices without paying the network-latency and distributed-transaction tax, and it's a common, deliberate stepping stone before splitting out services that actually need independent scaling.

Do microservices always mean better fault isolation?

Only if failures are actually handled at the boundary. If Orders calls Inventory synchronously and blocks forever when Inventory is slow or down, a failure in Inventory can still cascade and take down Orders — this is a well-known failure mode called cascading failure. Real fault isolation requires deliberate patterns: timeouts, circuit breakers, bulkheads, and fallback behavior, not just the fact that the services run in separate processes.

How do transactions work across microservices if there's no single ACID commit?

The common pattern is a saga: a sequence of local transactions, one per service, where each step publishes an event that triggers the next step, and if a later step fails, the earlier steps run compensating actions to undo their effect (e.g., a "cancel reservation" call to reverse an "reserve inventory" call). It trades the simplicity of a single all-or-nothing commit for eventual consistency and considerably more application-level code to handle the failure and rollback cases.

What is the "distributed monolith" anti-pattern?

It's what happens when a system is split into separate services and deployables, but they're still so tightly coupled — via synchronous calls that must all succeed, or a shared database, or a release process that requires deploying several services together — that it has all the operational cost of microservices (network calls, more infrastructure, harder debugging) with none of the independent-deployability benefit. It's widely considered the worst of both worlds.

How many services is "too many" to start with?

There's no fixed number — the right question is whether each proposed service boundary corresponds to a real difference in scaling needs, release cadence, or team ownership. Teams that decompose along those real boundaries tend to end up with a handful of coarse-grained services; teams that decompose by entity or database table alone often end up with far more services than they can operate well, each one too small to justify its own deployment and monitoring overhead.

🎓

Try our Software Engineering & Cloud Studio

More calculators, simulators, and guides for this discipline.

Related tools & guides

Microservices Communication PatternsSystem Design Interview GuideSystem Architecture PlannerDocker Compose Visualizer