Chapter 15 — Monolith vs Microservices
Chapter 15 — Monolith vs Microservices
Hey everyone! Welcome back to Namaste System Design! 🙏
This is the architecture debate everyone has an opinion on — and most opinions are wrong. Junior engineers think "microservices = modern = better." Senior engineers know it's a trade-off, and that starting with microservices is often a costly mistake. Let's give you the mature, interview-winning take.
What we will cover:
- What a monolith is (one big box)
- What microservices are (many small boxes)
- The restaurant-kitchen analogy
- The trade-offs, one dimension at a time — honestly
- How services actually talk to each other (sync vs async)
- The distributed transaction problem & the Saga pattern
- The hidden costs of microservices
- Which to start with (the senior answer)
- When NOT to use microservices
- Interview Questions
1. The Monolith — One Big Box
A monolith is a single application where all features live in one codebase and deploy together.
┌─────────────────────────────────┐
│ THE MONOLITH │
│ ┌────────┐ ┌────────┐ │
│ │ Users │ │ Orders │ │ ONE codebase
│ ├────────┤ ├────────┤ │ ONE deploy
│ │Payments│ │ Search │ │ ONE database (usually)
│ └────────┘ └────────┘ │
└─────────────────────────────────┘
deploys as a single unit
Every startup should basically start here. It's simple, fast to build, and easy to reason about.
2. Microservices — Many Small Boxes
Microservices split the app into small, independent services, each owning one business capability, its own database, and its own deployment.
┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐
│ Users │ │ Orders │ │Payments│ │ Search │
│ service│ │ service│ │ service│ │ service│
│ +DB │ │ +DB │ │ +DB │ │ +DB │
└───┬────┘ └───┬────┘ └───┬────┘ └───┬────┘
└───────────┴─── talk via APIs / queues ───┘
Each: own codebase, own DB, own deploy, own team.
3. The Restaurant Analogy
MONOLITH = one chef 👨🍳 who does EVERYTHING — starters,
mains, desserts, dishes. Simple when small.
But if that chef is sick, the whole kitchen stops.
And you can't "hire a dessert specialist" easily.
MICROSERVICES = separate stations: a starter chef, a grill
chef, a pastry chef 👨🍳👩🍳🧑🍳. Each is expert &
independent. Grill station busy? Add another grill
cook without touching pastry. But now they must
COORDINATE — and coordination is hard.
4. The Trade-offs, One Dimension at a Time
"Microservices are more complex" is true but vague. Here's exactly where that complexity shows up — and where the monolith pays a different price.
4.1 Simplicity
MONOLITH: one repo, one build, one place to search
for "where is checkout logic?" → grep the repo.
MICROSERVICES: the same logic might live in 3 repos, talking
over the network. "Where is checkout logic?" now
means tracing calls across services.
👉 Use it when: a small team and a small codebase — a monolith is simply easier to hold in your head.
4.2 Speed to Start
MONOLITH: clone repo, npm install, run. Shipping day 1.
MICROSERVICES: need service discovery, inter-service auth,
multiple CI/CD pipelines, multiple databases —
before you've shipped a single feature.
👉 Use it when: you're validating an idea and speed to market matters more than architectural purity — that's almost every early-stage product.
4.3 Scaling
Traffic spike hits ONLY the search feature.
MONOLITH: must scale the WHOLE app (users, payments,
everything) just to give search more capacity.
Wasteful — you're paying to scale code paths
nobody is hitting harder right now.
MICROSERVICES: scale just the search service. Add 5 more
search instances; payments and users don't move.
👉 Use it when: different parts of your system have wildly different, unpredictable load patterns.
4.4 Deployment
A bug is found in the payments code, day before a release.
MONOLITH: the WHOLE release is blocked — payments, users,
search, everything ships together or nothing does.
MICROSERVICES: only the payments service is held back.
Orders, search, users ship on schedule, unaffected.
👉 Use it when: teams need to ship independently, multiple times a day, without blocking each other.
4.5 Fault Isolation
The search feature has a memory leak and crashes.
MONOLITH: search runs in the SAME process as everything
else → crash can take down users, orders,
payments too. One bug, total outage.
MICROSERVICES: only the search service crashes. Users can
still log in, browse, and pay — search is just
temporarily broken.
👉 Use it when: you need one weak component to not endanger the whole system.
4.6 Team Autonomy
MONOLITH: every team commits to the SAME repo → merge
conflicts, shared release calendar, "who broke
the build?" finger-pointing as headcount grows.
MICROSERVICES: the payments team owns their service and
their deploy schedule completely. They don't
wait on the search team, and vice versa.
👉 Use it when: you have multiple independent teams, not multiple independent engineers on one team.
4.7 Debugging
A user reports "checkout is slow."
MONOLITH: one process, one stack trace, one set of logs.
Attach a profiler, done.
MICROSERVICES: the request touched the gateway, the users
service, the inventory service, the payments
service, and the notification service. WHICH one
was slow? You need distributed tracing (Ch 16)
just to answer that question.
👉 Use it when: you've already invested in observability tooling — otherwise this is the cost that surprises teams the most.
4.8 Data Consistency
"Place an order" needs to: create the order AND charge
the payment AND reserve inventory.
MONOLITH: one database → wrap it in ONE transaction.
Either all three happen, or none do. Easy.
MICROSERVICES: order DB, payment DB, and inventory DB are
THREE separate databases. No single transaction
can span all three. You inherit distributed
consistency problems (Chapter 14) — see §6 below.
👉 Use it when: you're prepared to redesign "transactions" as a multi-step process instead of a single atomic operation — this is often the trade-off teams underestimate most.
Visual Comparison
| Monolith | Microservices | |
|---|---|---|
| Simplicity | ✅ Simple to build, test, deploy | ❌ Complex — many moving parts |
| Speed to start | ✅ Fast (one codebase) | ❌ Slow (infra, networking, DevOps) |
| Scaling | ❌ Scale the WHOLE app even if only search is hot | ✅ Scale just the busy service |
| Deployment | ❌ One bug can block the whole release | ✅ Deploy services independently |
| Fault isolation | ❌ A crash can take everything down | ✅ One service fails, others survive |
| Team autonomy | ❌ Everyone in one codebase (merge pain) | ✅ Teams own services independently |
| Debugging | ✅ One place, easy stack traces | ❌ Hard — trace across many services (Ch 16) |
| Data consistency | ✅ Easy (one DB, transactions) | ❌ Hard (distributed data, no easy transactions) |
5. How Services Actually Talk to Each Other
Split the app into services, and you must now pick how they communicate. Two families:
5.1 Synchronous — call and wait
Order service ──REST/gRPC call──▶ Payment service
│ │
│◀─────── "charged ✅" ────────────┘
│
Order service now knows the outcome, continues.
Simple to reason about — feels like a normal function call.
✅ PROS Simple mental model. Caller gets an immediate answer.
❌ CONS Caller is BLOCKED waiting. If Payment is slow or down,
Order is stuck too — failures cascade (Chapter 08:
circuit breakers exist because of exactly this).
👉 USE IT WHEN The caller genuinely needs the answer before
it can continue (e.g. "was the charge approved?").
5.2 Asynchronous — fire an event, move on
Order service ──publishes──▶ [ "OrderPlaced" event ]
│
┌──────────────────┼──────────────────┐
▼ ▼ ▼
Payment svc Inventory svc Notification svc
(charges card) (reserves stock) (sends email)
Order service does NOT wait for any of them. Each
subscriber reacts on its own time, independently.
✅ PROS Caller isn't blocked. New subscribers can be added
later without touching the Order service at all.
❌ CONS Order service doesn't immediately know if payment
succeeded — the result comes back later, asynchronously.
Harder to reason about "did this actually work?"
👉 USE IT WHEN The caller doesn't need to wait for the result
right away, or many services need to react to
one event (this is the backbone of event-driven
architectures, often built on Kafka or SQS).
6. The Distributed Transaction Problem & the Saga Pattern
Back to §4.8. "Place an order" really means three separate database writes, in three separate services. What happens when the second one fails?
Step 1: Order service creates the order ✅ succeeds
Step 2: Inventory service reserves stock ✅ succeeds
Step 3: Payment service charges the card ❌ FAILS
(card declined)
Now what? The order exists. The stock is reserved. But the
payment never went through. Without a plan, you've sold an
item to nobody and paid for nothing. Data is now INCONSISTENT
across three databases, and there's no single transaction
that can roll all three back at once.
The fix is the Saga pattern: break the operation into a sequence of local transactions, and define a compensating action for each step — an "undo" — that runs if a later step fails.
FORWARD PATH: Order created ──▶ Stock reserved ──▶ Payment charged ──▶ DONE Payment FAILS → run compensations, in reverse: Stock reserved ──▶ [ COMPENSATE: release stock ] Order created ──▶ [ COMPENSATE: cancel order ] End state: order cancelled, stock released, nobody charged. Consistent again — just reached via a series of small, reversible steps instead of one big atomic transaction.
✅ PROS Each step is a normal local transaction — no need for
a fragile system-wide lock across three databases.
❌ CONS You must write a compensating action for every step,
and there's a real window where the system is
temporarily inconsistent (order exists, not yet
confirmed) — eventual consistency, applied to workflows.
👉 USE IT WHEN A single business operation spans multiple
services and their databases — this is the
standard answer to "how do transactions work
in microservices?" in an interview.
7. The Hidden Costs of Microservices
The brochure sells the benefits. Here's what it doesn't tell you:
❌ NETWORK is now everywhere → calls that were function calls are now network calls (slow, can fail, need retries). ❌ DISTRIBUTED transactions → covered in §6 above — no free lunch, every cross-service operation needs a Saga or similar. ❌ DEBUGGING is painful → one user request touches 10 services. Where did it break? (This is why we need distributed tracing.) ❌ OPERATIONAL overhead → dozens of deployments, service discovery, monitoring, a whole DevOps/Kubernetes practice. ❌ EVENTUAL CONSISTENCY → separate DBs drift; you inherit all the CAP/consistency problems from Chapters 09 & 14.
The famous warning: "Microservices solve an organizational problem (many teams stepping on each other), not a technical one. If you have one small team, they mostly add pain."
8. Which Should You Start With?
┌─────────────────────────────────────────────────────────────┐
│ THE SENIOR ANSWER: "Start with a monolith." │
│ │
│ → Build a well-structured ("modular") monolith first. │
│ → Split OUT microservices later, ONLY when a real pain │
│ appears: a team is too big, or one part needs to scale │
│ very differently. │
│ │
│ This is the "MonolithFirst" strategy. Amazon, Netflix, │
│ Shopify all STARTED as monoliths and split later. │
└─────────────────────────────────────────────────────────────┘
When microservices genuinely make sense: large org with many independent teams; parts of the system with wildly different scaling needs; the need to deploy pieces independently many times a day.
9. When NOT to Use Microservices
The flip side, stated plainly — because "it depends" isn't a satisfying interview answer on its own.
❌ Small team (a handful of engineers) — there's nobody for the extra services to give autonomy TO. It's pure overhead. ❌ Unclear or fast-changing domain boundaries — if you don't yet know where "orders" ends and "payments" begins, you'll draw the service boundaries wrong and pay to redraw them. ❌ Pre-product-market-fit — you need to iterate on the PRODUCT fast. Cross-service coordination slows down exactly the kind of rapid, sweeping changes an early product needs. ❌ No dedicated infra/DevOps capability — someone has to run service discovery, monitoring, CI/CD for N services, and on-call for N things that can each fail independently. ❌ Most operations need strong, immediate consistency across the whole dataset — if nearly every action is a cross-cutting transaction, you'll spend all your time fighting distributed consistency instead of building.
👉 Rule of thumb: microservices are a tool for organizational scale, not a badge of technical maturity. If you can't name the team that's in pain today, you probably don't need them yet.
Interview Questions — Quick Fire!
Q: What's the difference between a monolith and microservices?
"A monolith is a single application where all features share one codebase, one deployment, and usually one database. Microservices split the application into small, independent services, each owning a business capability with its own database and deployment, communicating over APIs or message queues. Monoliths are simpler; microservices offer independent scaling and deployment at the cost of significant complexity."
Q: What are the main benefits of microservices?
"Independent scaling — you scale only the busy service; independent deployment — teams ship without coordinating one big release; fault isolation — one service failing doesn't necessarily bring down the rest; and team autonomy — separate teams own separate services and move faster. They shine when you have many teams and components with very different scaling needs."
Q: How do services communicate, and what's the trade-off?
"Synchronously, like REST or gRPC calls, where the caller waits for a direct answer — simple, but the caller is blocked and failures can cascade if the callee is slow or down. Or asynchronously, publishing an event that other services subscribe to and react to independently — the caller isn't blocked and new subscribers can be added without touching the publisher, but you lose the immediate 'did it work?' answer. Most real systems use both: synchronous for calls that truly need an immediate result, asynchronous for everything else."
Q: How do you handle a transaction that spans multiple microservices?
"You can't use a single database transaction across services, so you use the Saga pattern: break the operation into a sequence of local transactions, each in its own service, and define a compensating action for each step that undoes it if a later step fails. For example, placing an order might create the order, reserve stock, then charge payment — if the charge fails, you run compensations in reverse: release the stock, then cancel the order. It trades atomicity for a temporary window of inconsistency that resolves itself."
Q: What are the downsides of microservices?
"They add a lot of complexity: network calls replace function calls and can fail; transactions across services become hard, needing patterns like Saga; debugging a request that spans many services requires distributed tracing; and there's heavy operational overhead — service discovery, many deployments, monitoring, orchestration. You also inherit distributed data consistency problems."
Q: Should a new startup start with microservices?
"Generally no. The mature advice is to start with a well-structured monolith, which is faster to build and easier to operate, and only extract microservices later when a concrete pain emerges — like a team growing too large or a component needing very different scaling. Many big companies started as monoliths and split gradually. Microservices mostly solve an organizational scaling problem, not a technical one."
Q: When should you explicitly avoid microservices?
"When the team is small, when domain boundaries aren't settled yet, when you're still searching for product-market fit and need to move fast across the whole system, when there's no one to own the operational overhead, or when most operations need strong consistency across the whole dataset. In all of those cases, the coordination cost of microservices outweighs any benefit."
Key Points to Remember
| Concept | Key Takeaway |
|---|---|
| Monolith | One codebase/deploy/DB. Simple, fast to start, easy to debug. Scales as a whole. |
| Microservices | Many independent services. Independent scale/deploy + fault isolation, but complex. |
| Communication | Sync (REST/gRPC, blocks, can cascade) vs async (events, non-blocking, delayed result). |
| Saga pattern | No cross-service transactions — chain local transactions + compensating "undo" steps. |
| Hidden costs | Network everywhere, distributed transactions, hard debugging, ops overhead. |
| Start with | A modular monolith. Split out services only when a real pain appears. |
| When NOT to | Small team, unclear boundaries, pre-PMF, no DevOps capacity, need for pervasive strong consistency. |
| Real reason | Microservices solve an organizational problem (many teams), not a technical one. |
What's Next?
Once you have many services and machines, a scary question appears: "It's broken — but WHERE?" Chapter 16 covers observability — logs, metrics, and traces — the tools that let you see inside a running system.
Keep designing, keep scaling! See you in the next one!
Post a Comment