HT
How Things Work
System Online

Monolith vs Microservices

When to split and when not to — independent deployability and scaling vs the very real costs of distribution, and how the strangler fig pattern migrates safely.

How It Works

A monolith packages all functionality into one deployable unit; microservices split it into independently deployable services, each owning a capability and its data. Microservices buy independent deployment, scaling, and team autonomy — but charge you in network calls, distributed data, partial failure, and operational complexity. The right choice depends on scale, team size, and whether clear boundaries exist.

1
The Monolith

A monolith is a single deployable unit: all modules run in one process and usually share one database. It's the simplest way to start — easy local dev, simple deployment, fast in-process calls, and straightforward transactions across modules. Most successful systems begin here, and many should stay.

2
Why Split

Pain drives the split: a huge codebase slows every build and deploy, one module's scaling needs differ wildly from others, teams step on each other, and a bug in one area can crash the whole app. Microservices let teams deploy, scale, and choose technology independently per service.

3
The Hidden Costs of Distribution

Splitting trades in-process method calls for network calls — now you face latency, partial failure, retries, distributed transactions, eventual consistency, service discovery, and far more operational complexity. A function call that couldn't fail becomes an RPC that can time out. Distribution is not free.

4
Strangler Fig Migration

Don't rewrite — strangle. Put a gateway in front, then extract one capability at a time into a service, routing that traffic to the new service while the monolith handles the rest. Over time the new services 'strangle' the monolith. You ship continuously and can roll back any step.

Key Concepts

🧱Monolith

One deployable unit, typically one database. Simple to build/run; scaled and deployed as a whole.

🧩Microservice

An independently deployable, independently scalable service owning its own data and a single capability.

🚀Independent Deployability

The core benefit: ship one service without redeploying everything else — smaller blast radius.

🌿Strangler Fig

Incrementally extracting services behind a gateway until the monolith is replaced — no big-bang rewrite.

⚠️Distributed Monolith

The worst of both: services that must deploy together and call each other synchronously. An anti-pattern.

✂️Service Boundary

A split aligned with a business capability / bounded context, minimizing chatty cross-service calls.

Strangler fig extraction
tsx
1// The Strangler Fig: extract a service without a big-bang rewrite.
2// Route most traffic to the monolith, peel off one capability at a time.
3
4// API gateway / reverse proxy routing:
5// /api/search/* → new SearchService (extracted)
6// /api/* → legacy Monolith (everything else, for now)
7
8// In code, hide the seam behind an interface the monolith already uses:
9interface SearchProvider { query(term: string): Promise<Result[]>; }
10
11class LegacySearch implements SearchProvider { /* in-process */ }
12class RemoteSearch implements SearchProvider { // new microservice
13 async query(term: string) {
14 return httpClient.get(`${SEARCH_URL}/search?q=${term}`); // network hop
15 }
16}
17
18// Flip a feature flag to shift traffic gradually; roll back instantly if needed.
19const search: SearchProvider = flags.useRemoteSearch
20 ? new RemoteSearch()
21 : new LegacySearch();
💡
Why This Matters

The monolith-vs-microservices decision shapes a whole system's cost, velocity, and reliability, and it's one of the most common (and most over-simplified) architecture-interview topics. The mature answer isn't 'microservices are better' — it's understanding the trade-offs well enough to start simple and split deliberately when the pain justifies it.

Common Pitfalls

Choosing microservices by default / for résumé reasons before you have the scale or team size to need them.
Splitting along technical layers instead of business capabilities, creating chatty, coupled services.
Building a distributed monolith — services that must deploy together and call each other synchronously.
Sharing one database across services, recreating tight coupling and defeating independence.
Big-bang rewrites instead of incremental strangler-fig migration — high risk, long no-value periods.
Real-World Use Cases

1One Module Needs to Scale 50× More Than the Rest

Scenario

An e-commerce monolith is fine except during sales, when the search and product-listing load spikes 50× while checkout volume barely changes. Scaling means duplicating the entire app — wasteful and expensive.

Problem

In a monolith you scale everything together. Provisioning enough instances to handle search's spikes means paying for 50× of payments, admin, and reporting too — capacity you don't need.

Solution

Extract Search (and product catalog) into its own service using the strangler fig pattern. Now that service auto-scales independently to handle spikes, while checkout and the rest stay at a steady, cheap baseline. The split is justified by a real, divergent scaling need.

💡

Takeaway: Extract a service when one part has genuinely different scaling, deployment, or team needs. Independent scalability is a top reason to split — but only for the parts that actually need it.

2The Distributed Monolith Trap

Scenario

A team eagerly split a small app into 12 microservices. Now every feature touches 5 services that must be deployed together in a specific order, services call each other synchronously in long chains, and a single page load fans out into 20 network hops.

Problem

They distributed without finding real boundaries, creating a 'distributed monolith' — all the operational pain of microservices (network, ops, debugging) with none of the independence. Tight synchronous coupling means nothing can deploy or fail alone.

Solution

Consolidate over-eager splits back toward a modular monolith, keeping clear module boundaries in one deployable. Extract services later only where a boundary is proven by divergent scaling, team ownership, or release cadence — and prefer async messaging over synchronous call chains.

💡

Takeaway: Microservices are an organizational and scaling tool, not a default. Start with a well-structured (modular) monolith; premature splitting yields a distributed monolith that's harder than either pure option.

Join the discussion

Comments

?

Loading comments...

Cookie preferences

We use essential cookies for sign-in and preferences. With your permission, we also use basic analytics to improve lessons.