Filter concepts by levelShowing all levels.

System Design · Section 10

Monolith vs Modular Monolith vs Microservices

Level
intermediate
Read
24 min
Concepts
5

The advantages side of all three architectural shapes, weighed against each other — a plain monolith's simpler deployment, local calls, easier transactions and lower operational overhead; a modular monolith's clearer boundaries without distributed-system cost; and microservices' independent scaling, deployment and team ownership — paired with the six concrete costs microservices pay for that independence, and closed by the discipline that decides between them: splitting a component out only for a specific, named pain, never speculatively.

This section

What is true here

  1. Monolith: simpler deployment, local (in-process) calls, easier ACID transactions, lower operational overhead.
  2. Modular monolith: clearer, enforced boundaries and lower coupling — without paying the network cost microservices requires.
  3. Microservices: independent scaling, independent deployment, independent team ownership.
  4. Microservices costs: network calls, distributed transactions, harder observability, more operational overhead, eventual consistency, new failure modes (cascading failure, retry storms).
  5. Split a component into its own service only for a specific, named pain that outweighs those costs — never speculatively, "just in case."

What you will be able to do

  • List the four advantages a plain monolith has over a distributed architecture
  • Explain what a modular monolith buys over a plain monolith, and why it does not cost what microservices does
  • List the three advantages microservices provide and the six costs they are paid for with
  • Apply a specific-pain checklist to decide whether a given component justifies being split into its own service

The advantages

What each of the three shapes buys, from the simplest (monolith) to the most independent (microservices).

Monolith advantages

corebeginner

A monolith deploys as one unit, calls between components are local function calls, a single database means transactions are straightforward, and there is only one thing to operate — one log stream, one deploy pipeline, one set of alerts.

Think of it as

A monolith concentrates everything in one place, which is exactly what makes each of these four advantages true. One deployable means one deploy step, not coordinating a release across a dozen services. In-process calls mean no network, no serialization, no partial failure. One database means an operation touching multiple tables can be wrapped in a single ACID transaction. And one running system means one thing to monitor, one place logs live, one on-call surface — not fifteen.

text
one deployable + in-process calls + one database
  = simpler deploys, no network between components,
    real ACID transactions, one thing to operate

What we're doing: Show a cross-cutting operation that is trivial in a monolith, to make the four advantages concrete together.

monolith-advantages.txttext
Feature: cancelling an order must also restock
inventory and issue a refund, atomically.

Monolith:
  BEGIN TRANSACTION;
    UPDATE orders SET status = 'cancelled' ...;
    UPDATE inventory SET qty = qty + 3 ...;
    INSERT INTO refunds (...) VALUES (...);
  COMMIT;
  -- one transaction, one deploy, one dashboard
  -- to check if this operation is failing
5
All three writes are in one transaction, on one database — either all succeed or all roll back, with no extra coordination protocol.
9
Debugging a failure here means checking one log stream and one dashboard, not correlating across several services.

Why this works: These four advantages are strongest exactly when an operation naturally spans multiple concerns (orders, inventory, refunds) — a monolith keeps that operation simple, while the same operation in a distributed system needs a distributed transaction pattern (like sagas) just to get the same atomicity.

Dismissing monolith advantages as merely "legacy" or "old-fashioned"

Wrong

text
"Monoliths are outdated — everyone should
use microservices now."

Better

text
"A monolith gives us real ACID transactions
and one deploy pipeline — real advantages for our
current team size and consistency needs, not a
sign we're behind."

What you see: A team migrates off a working monolith and immediately loses simple cross-table transactions, replacing them with a distributed saga pattern that takes weeks to implement correctly for a guarantee the monolith provided for free.

Why: Monolith advantages are not a sign of technical debt — they are real properties (simpler transactions, lower operational surface) that a distributed architecture has to deliberately re-earn through additional patterns and tooling, at real engineering cost.

What concentrating everything in one place buys

Simpler deployment

one build, one deploy step

Local calls

in-process, no network

Easier transactions

one database, real ACID

Lower ops overhead

one thing to monitor

  • Simpler deployment — one build, one deploy step
  • Local calls — in-process, no network
  • Easier transactions — one database, real ACID
  • Lower ops overhead — one thing to monitor

The four monolith advantages

The four monolith advantages
AdvantageWhat it meansWhat it avoids
Simpler deploymentone build, one deploy stepcoordinating a multi-service release
Local callsin-process function calls between componentsnetwork latency, serialization, partial failure
Easier transactionsone database, real ACID transactions across tablesdistributed transaction protocols
Lower operational overheadone running system to monitor and operatemanaging N separate deploy pipelines and dashboards

Together

text
Placing an order and decrementing stock,
in a monolith with one database:

  BEGIN TRANSACTION;
    INSERT INTO orders (...) VALUES (...);
    UPDATE inventory SET qty = qty - 3 WHERE item_id = 42;
  COMMIT;

Both writes succeed or both roll back — a single
ACID transaction. No distributed transaction
protocol needed.

Remember: Monolith: simpler deployment (one build), local calls (no network), easier transactions (one database, real ACID), lower operational overhead (one thing to run).

See also: modular monolith advantages · microservices advantages

Modular monolith advantages

standardintermediate

A modular monolith keeps the plain monolith's single deploy and local calls, while adding enforced boundaries between modules — clearer ownership and lower coupling, without paying for a network between components.

Think of it as

It sits directly between a plain monolith and microservices, and inherits the best of the side it is closer to on each axis: like a plain monolith, one deploy, one database technology choice, in-process calls (no network cost). Like microservices, real module boundaries and clear data ownership (lower coupling cost). It is specifically the option that says "we want the coupling reduction without paying for the network," which is exactly the coupling-vs-network trade-off a team that has outgrown "no boundaries" but not yet outgrown "one deployable" is looking for.

text
plain monolith:     no boundaries + no network cost
modular monolith:    real boundaries + no network cost
microservices:        real boundaries + network cost

modular monolith gets the boundary benefit
without the network cost

What we're doing: Show a codebase outgrowing a plain monolith and choosing a modular monolith specifically to avoid paying for microservices it does not yet need.

modular-monolith-advantages.txttext
A growing team (12 engineers, 3 squads) hits
a real problem in their plain monolith: the
Orders squad's changes keep breaking Inventory
squad's code because both freely query each
other's tables.

Considered: full microservices split.
  Cost: 3 new deploy pipelines, service discovery,
  network failure handling — for a team that has
  no actual scaling problem yet.

Chosen: modular monolith.
  Orders and Inventory become separate modules,
  each owning its own tables, communicating only
  through defined interfaces. Still one deploy,
  still one database technology, still no network
  calls between them — but the coupling problem
  that was actually hurting them is fixed.
4
The actual pain point is coupling from shared table access — not a scaling or deployment problem.
8
Microservices would fix the coupling but also add real costs the team has no matching need for yet.
13
A modular monolith fixes exactly the coupling problem, without adding any of the network or deployment cost microservices would have required.

Why this works: Matching the fix to the actual problem matters — this team's problem was coupling, not scaling or independent deployment, so a modular monolith solves it without paying for capabilities (independent scaling, independent deploys) the team does not yet need.

Assuming boundary problems always require a microservices split to fix

Wrong

text
"Our modules keep stepping on each other —
we need microservices."

Better

text
"Our modules keep stepping on each other
because nothing enforces boundaries between them
— let's enforce module boundaries and data
ownership first, inside one deployable, and see
if that alone fixes it."

What you see: A team pays the full network and deployment cost of microservices to fix a coupling problem that enforced module boundaries inside a single deployable would have solved just as well, for a fraction of the operational cost.

Why: Coupling and independent scaling/deployment are different problems with different fixes — a modular monolith addresses coupling directly, and is very often sufficient on its own without also needing the network boundary microservices adds.

Two independent axes — and the option that only moves along one
Plain monolith
One deploy, in-process calls, and nothing stopping the Orders code from querying the Inventory tables.
Modular monolith
Moves right without moving up. Modules own their own tables and talk through defined interfaces, still inside one deployable.
Microservices
The same boundary benefit, now paid for with deploy pipelines, service discovery and network failure handling.
  • Plain monolith: no enforced boundaries, no network between components — One deploy, in-process calls, and nothing stopping the Orders code from querying the Inventory tables.
  • Modular monolith: real module boundaries, no network between components — Moves right without moving up. Modules own their own tables and talk through defined interfaces, still inside one deployable.
  • Microservices: real module boundaries, network between components — The same boundary benefit, now paid for with deploy pipelines, service discovery and network failure handling.

Remember: A modular monolith gets clearer boundaries and lower coupling, same as microservices, without paying the network and deployment cost microservices requires.

See also: monolith advantages · modular monolith pattern

Microservices advantages

coreintermediate

Microservices let each service scale independently (only the hot service gets more instances), deploy independently (one team's release does not require coordinating with every other team), and be owned independently (a team can fully own a service's code, data, and on-call).

Think of it as

These three advantages all come from the same root property: services are fully separated, not just logically (like a modular monolith's modules) but operationally. Independent scaling means a traffic spike hitting one service does not require scaling the whole system. Independent deployment means Team A can ship ten times a day while Team B ships weekly, with no coordination between them. Independent ownership means a team can make its own technology choices, its own schema decisions, and be the sole owner of its service's reliability — a property a modular monolith, with everyone still sharing one deploy, cannot offer.

text
each service: own instances (scale) + own pipeline
(deploy) + own team (ownership)

none of the three require any other service to
change, deploy, or scale in lockstep

What we're doing: Show all three advantages exercised together in one scenario, and what a monolith would have required instead.

microservices-advantages.txttext
Black Friday: checkout traffic spikes 20x,
catalog browsing traffic stays flat.

  Independent scaling:    checkout service scales
    from 5 to 100 instances; catalog service stays
    at 5 — no wasted capacity scaling what doesn't
    need it
  Independent deployment: the checkout team ships
    3 hotfixes during the event without touching
    or redeploying the catalog team's service
  Independent ownership:  the checkout team is
    solely on-call for checkout during the event,
    with no cross-team coordination needed to
    respond to an incident

A monolith handling this spike would scale (and
redeploy) the entire system to handle load that
only one part of it actually needs.
4
Scaling only the service under load avoids the waste of scaling parts of the system with no traffic increase.
8
The checkout team ships hotfixes without any coordination with, or risk to, the catalog team's service.
12
On-call ownership stays scoped to the team that actually owns the affected service.

Why this works: These advantages matter most exactly when different parts of a system have different scaling needs, different release cadences, or are owned by genuinely separate teams — a monolith (plain or modular) forces all three to move together regardless.

Claiming "independent deployment" while services still share a deploy pipeline

Wrong

text
"We have microservices" — but all 6 services
are built and deployed together from one CI
pipeline, gated by all 6 test suites passing.

Better

text
Each service has its own pipeline: checkout-svc
deploys independently of catalog-svc, gated only
by checkout-svc's own tests.

What you see: A team splits code into separate services but keeps one shared CI/CD pipeline — any service's failing test blocks every other service's deploy, so none of the promised independent-deployment benefit is actually realized.

Why: Independent deployment is an operational property, not just a code-organization one — services that are logically separate but still deploy through one shared pipeline have paid the network cost of microservices without earning the deployment-independence benefit.

What true operational separation buys

Independent scaling

only the hot service scales

Independent deployment

no cross-team release coordination

Independent ownership

a team fully owns code, data, on-call

  • Independent scaling — only the hot service scales
  • Independent deployment — no cross-team release coordination
  • Independent ownership — a team fully owns code, data, on-call

The three microservices advantages

The three microservices advantages
AdvantageWhat it meansRequires
Independent scalinga hot service scales alone, not the whole systemthe service running as its own set of instances
Independent deploymentone team ships without coordinating with othersa separate deploy pipeline per service
Independent ownershipa team fully owns a service — code, data, on-callthe service being the unit a team is organized around

Together

text
A video platform under a traffic spike:

  Video transcoding service: CPU-bound, gets a
    spike in uploads -> scales to 50 instances
  User profile service: unaffected by the spike
    -> stays at 3 instances

Scaling only the transcoding service (independent
scaling) would be impossible in a monolith, where
scaling means scaling the entire deployable.

Remember: Microservices buy independent scaling (only the hot service scales), independent deployment (no cross-team coordination to ship), and independent team ownership — all three require true separation, not just separate code.

See also: microservices costs · microservices pattern

Advertisement

The costs and the discipline

What microservices specifically cost for their independence, and the checklist that decides whether a split is actually justified.

Microservices costs

coreintermediate

Microservices pay for their advantages with six concrete costs: network calls replacing function calls, distributed transactions replacing simple ACID ones, harder observability across many services, more operational overhead (multiple pipelines, multiple dashboards), eventual rather than immediate consistency, and new failure modes a monolith never has.

Think of it as

Every advantage in the previous concept has a matching cost here, because independence is exactly what creates these costs. Independent deployment means independent operational surfaces (more pipelines, more dashboards). Independent data ownership means a transaction spanning two services can no longer be one ACID transaction — it needs a distributed pattern like a saga, and the data is consistent eventually, not immediately. And every one of those in-process calls the monolith had for free is now a network call, which times out, retries, and needs a plan for the other service being down.

text
monolith:      1 transaction, 1 dashboard, always
                consistent, calls never "fail" to reach code
microservices: N services = N network calls that can
                fail, N things to monitor, eventual
                consistency, and failure that can cascade

What we're doing: Trace one operation through the costs of a distributed failure, to make the abstract list concrete.

microservices-costs.txttext
Billing service (one of eight in the system)
starts responding slowly under load.

  Network calls:      every service calling Billing
    now waits longer per request
  Failure mode:        Checkout retries failed calls
    to Billing, which ADDS more load to an already
    struggling service -> a retry storm
  Cascading failure:   Checkout's own request queue
    backs up waiting on Billing, and Checkout starts
    timing out for callers that have nothing to do
    with billing
  Observability:       diagnosing this requires
    distributed tracing across Checkout -> Billing,
    not just one stack trace
  Operational overhead: on-call now needs dashboards
    for both services, correlated by request ID,
    to see the full picture
6
A slowdown in one service directly costs latency in every service calling it — a cost a monolith's in-process calls never pay.
9
Retries without backoff can make a struggling service worse, not better — a failure mode with no equivalent inside one process.
13
The failure cascades to a service (Checkout) that has nothing to do with the original problem (Billing).
17
Understanding what happened requires tracing a request across service boundaries, not reading one log file.

Why this works: These costs are why "just split it into microservices" is never free — the six costs listed here are the real, ongoing engineering and operational tax that independence requires, and they compound with each other under real failure conditions, as this single incident shows.

Adding cross-service calls without a circuit breaker or backoff, enabling a retry storm

Wrong

text
// naive retry: hammer the struggling service
// harder every time it fails
while (true) {
  try { return await callBilling(); }
  catch { continue; } // retries immediately, forever
}

Better

text
// exponential backoff + circuit breaker: stop
// calling a service that's already struggling
const result = await circuitBreaker.call(
  () => callBilling(), { backoff: 'exponential', maxRetries: 3 },
);

What you see: A single slow service triggers every caller to retry aggressively, which multiplies the load on the already-struggling service and turns a minor slowdown into a full outage across multiple services.

Why: Retries without backoff or a circuit breaker actively make a distributed failure worse — this exact failure mode does not exist in a monolith, where a slow code path just makes one process slow, not a cascading, self-amplifying outage across services.

"Cancel order" spanning three services
Client
Orders
Inventory
Billing
  1. 1. cancel order
  2. 2. commit locallyOrders now shows "cancelled" immediately
  3. 3. order.cancelled event
  4. 4. order.cancelled event
  5. 5. release reservationprocessed on its own schedule — still shows "reserved" until this runs
  6. 6. void the chargeif this call fails, a saga/compensation step is needed — no single ACID commit covers all three
  1. Client → Orders: cancel order
  2. Orders → Orders: commit locally (Orders now shows "cancelled" immediately)
  3. Orders → Inventory: order.cancelled event
  4. Orders → Billing: order.cancelled event
  5. Inventory → Inventory: release reservation (processed on its own schedule — still shows "reserved" until this runs)
  6. Billing → Billing: void the charge (if this call fails, a saga/compensation step is needed — no single ACID commit covers all three)

The six microservices costs

The six microservices costs
CostWhat changesWhat a monolith had instead
Network callsin-process call → HTTP/gRPC calla function call, always available, no latency
Distributed transactionsACID across tables → saga across servicesone BEGIN/COMMIT across all affected tables
Observabilityone request may span N servicesone log stream, one stack trace
Operational overheadN deploy pipelines, N dashboardsone pipeline, one dashboard
Eventual consistencyother services see a change with delayevery read sees the latest committed write
New failure modespartial failure, cascading failure, retry stormsa bug crashes the one process — no cross-service cascade

Together

text
"Cancel order" spanning Orders, Inventory
and Billing services:

  Monolith: one transaction, all three tables,
            atomic, instantly consistent

  Microservices: Orders publishes "order.cancelled",
    Inventory and Billing each process it on their
    own schedule -> for a window, Orders shows
    "cancelled" while Inventory still shows the
    item reserved (eventual consistency)

Remember: Microservices cost: network calls (latency, timeouts), distributed transactions (sagas, not ACID), harder observability, more operational overhead, eventual consistency, and new failure modes like cascading failure and retry storms.

See also: microservices advantages · when not to split

Know when not to split into microservices

coreintermediate

Splitting into microservices is not the default good choice — it is justified only when a real, specific pain (independent scaling need, independent team ownership, independent deploy cadence) outweighs the network, transaction, and operational costs. Without that pain, staying a monolith or modular monolith is the better call.

Think of it as

Every microservices cost from the previous concept is paid up front, while the benefits (independent scaling, deployment, ownership) only materialize once a team actually experiences the specific pain those benefits solve. A 3-person team with one release cadence gets none of the deployment-independence benefit (there is only one team to coordinate with — itself) while still paying the full network and transaction cost. The right question is never "could this be microservices" — almost anything could — it is "which of these costs are we already forced to pay some other way, and does a specific pain justify each one individually."

text
before splitting a component out, name the SPECIFIC
pain it fixes:
  "X needs to scale independently because ___"
  "Team Y needs to deploy independently because ___"
  "Team Z needs to own this because ___"

no specific answer -> don't split yet

What we're doing: Contrast a team that should split one component with a team that should not split anything, using the same checklist.

when-not-to-split.txttext
Case A - a video platform:
  Transcoding is CPU-bound and spikes 50x during
  peak upload hours; everything else stays flat.
  -> real, specific scaling difference. Splitting
     out transcoding is justified.

Case B - the same platform's user-profile and
settings pages:
  Same team, same release cadence, similar (low,
  flat) load, no independent scaling need.
  -> no specific pain identified. Splitting these
     into separate services would add network and
     operational cost for no matching benefit.
4
Transcoding's scaling profile is measurably different from the rest of the system — a concrete, specific reason to split.
11
The profile/settings pages have no such difference — splitting them would be cost with no matching benefit.

Why this works: Applying the same checklist to two different parts of one system shows the decision is per-component, not all-or-nothing — a system can reasonably be a modular monolith with exactly one component (transcoding) split out as its own service, rather than either "monolith" or "microservices" applied uniformly.

Splitting a system into microservices pre-emptively, before any specific pain exists

Wrong

text
"We might need to scale different parts
independently someday, so let's build it as
microservices from the start."

Better

text
"Start as a modular monolith. When a specific
component shows a real, different scaling or
ownership need, split THAT component out — not
speculatively, but in response to a need we can
actually name."

What you see: A small team spends significant early engineering time on service boundaries, network reliability, and multiple deploy pipelines for a scaling or ownership need that never actually materializes, while feature work that would have proven the product slows down.

Why: Splitting for a hypothetical future need pays the real, certain cost of microservices for a speculative, uncertain benefit — the discipline is to split components in response to a specific, observed pain, not in anticipation of one that may never arrive.

Same checklist, two components of one video platform

Transcoding — split it

  • +CPU-bound, spikes 50x at peak uploads
  • +Everything else stays flat
  • +A real, specific scaling difference

Profile/settings — don't split

  • Same team, same release cadence
  • Similar, low, flat load
  • No specific pain identified
  • Transcoding — split it
    • CPU-bound, spikes 50x at peak uploads
    • Everything else stays flat
    • A real, specific scaling difference
  • Profile/settings — don't split
    • Same team, same release cadence
    • Similar, low, flat load
    • No specific pain identified

Signals for and against splitting

Signals for and against splitting
SignalSuggests
One team owns the whole codebasestay monolith/modular monolith — no ownership boundary to split along
All components have similar, correlated loadstay monolith — independent scaling buys nothing
One release cadence, one on-call rotationstay monolith — no deployment coordination to remove
A specific component has a genuinely different scaling profilea real reason to split that one component out
Distinct teams want to own distinct services independentlya real reason to split along team boundaries
No experience operating distributed systems yetbuild that operational maturity before adding distributed failure modes

Together

text
A 5-person startup's MVP: one team, one release
cadence, all components under similar load, no
prior experience running distributed systems.

Every signal points at: stay a monolith (or a
modular monolith once boundaries start to hurt).
None of the "split" signals are present yet.

Remember: Split a component into its own service only when a specific, named pain (real scaling difference, real deployment or ownership boundary) justifies the network and operational cost — not speculatively.

See also: microservices costs · pattern tradeoffs

Advertisement