Monolith vs Microservices: An Honest Decision Guide
Backend DevelopmentLesson 1 of 45 min read
What you'll learn
- Define monolith, modular monolith and microservices by deployable unit, not by size
- Quantify what a network hop costs in availability using simple arithmetic
- List the genuine benefits of services and the organisational conditions they need
- Run a pre-split checklist and decide when staying monolithic is the right answer
On this page
A nine-person team runs a grocery-delivery startup on a single web application. The CTO returns from a conference and announces that the platform will be split into microservices by quarter end. Six months later the team owns fourteen repositories, a flaky staging environment that nobody can fully run on a laptop, and a feature that used to be one pull request is now four. Was the split wrong? Not necessarily. It was never examined. This lesson gives you the tools to examine it.
Three shapes, defined by what you deploy
Forget lines of code. The useful definition is how many things you release and run separately.
- Monolith: one deployable unit. Everything (catalogue, cart, payments, delivery slots) ships together and usually shares one database. A monolith can be well structured or a tangle; "monolith" describes deployment, not quality.
- Modular monolith: still one deployable unit, but with enforced internal boundaries. Modules expose a small public interface, never reach into each other's tables, and could be extracted later with moderate effort.
- Microservices: several independently deployable services, each owning its own data, communicating over a network. Independence is the point: a service can be changed, released and scaled without coordinating a release of the others.
What the network takes from you
Inside one process, calling cartService.total(cartId) either returns or throws, takes microseconds and cannot be half-delivered. Across a network the same call can time out after the other side already acted, return slowly, or fail because a router somewhere restarted. You also lose transactions that span the call (the topic of the last lesson). The cheapest way to feel this is arithmetic: if a request must pass through several services in a row, their availabilities multiply.
# Availability of a request that must touch every service in a chain.
per_service = 0.999 # 99.9% each
for hops in (1, 3, 6, 10):
chain = per_service ** hops
downtime_min = (1 - chain) * 30 * 24 * 60 # minutes in a 30-day month
print(f"{hops:>2} services in the call path -> {chain * 100:.2f}% available, "
f"about {downtime_min:.0f} min/month unavailable")Output
1 services in the call path -> 99.90% available, about 43 min/month unavailable 3 services in the call path -> 99.70% available, about 129 min/month unavailable 6 services in the call path -> 99.40% available, about 259 min/month unavailable 10 services in the call path -> 99.00% available, about 430 min/month unavailable
The model is simplified: it assumes failures are independent and that every service is required for the request to succeed. Real systems soften this with timeouts, fallbacks and caching, and they also make it worse with retries and shared dependencies. But the direction is reliable. Every synchronous hop you add is a place for the request to fail, and you must design for it on purpose.
What services genuinely buy you
There are real benefits, and each one depends on something you have to bring yourself.
| Benefit | What it requires from you | If you lack it |
|---|---|---|
| Independent deploys | Backward-compatible APIs, automated tests, a working pipeline | Lock-step releases, which is a monolith with extra steps |
| Independent scaling | A component with a truly different load profile | More infrastructure for no measurable gain |
| Team autonomy | Several teams that are blocked by one shared codebase | One team juggling many repositories |
| Fault isolation | Timeouts, bulkheads and graceful degradation | One slow service takes every caller down with it |
| Technology choice | People willing to operate each stack | Pager duty for five runtimes |
Notice that most rows are about people and process more than about code. That is why the guidance below keeps returning to team size and delivery maturity. If you want the capacity side of the story, scalability and load balancing shows that a monolith behind a load balancer scales further than many people assume.
A checklist to run before you split
Answer these honestly, in writing, with the team. Each "no" in the first group is a reason to wait; each "yes" in the second group is a reason to consider splitting something specific rather than everything.
READINESS (all should be YES before splitting)
[ ] Can we deploy the monolith to production on demand, safely, in minutes?
[ ] Do we have automated tests that catch breaking changes?
[ ] Can we trace one request across components (logs with a request id)?
[ ] Do we know our domain well enough to name the seams?
[ ] Is someone on call who can debug a distributed failure?
PRESSURE (YES here points at WHICH piece to extract)
[ ] Do two or more teams collide in the same module every week?
[ ] Does one component need 20x the capacity of the rest?
[ ] Does one component need a different release cadence or compliance scope?
[ ] Is one component's failure currently taking down unrelated features?
DECISION RULE
No pressure items -> stay a (modular) monolith.
Pressure, not ready -> fix the readiness gaps first.
Pressure and ready -> extract ONE service, run it for a quarter, then decide.Common mistakes and the symptoms they cause
- Splitting by technical layer (a UI service, a business-logic service, a database service). Symptom: every feature needs changes in all of them, so deploys are coupled anyway.
- Splitting before the domain is understood. Symptom: boundaries move every month, and each move means rewriting APIs and migrating data between services.
- A shared database. Symptom: a column rename in one service breaks three others at runtime, and nobody can tell which code reads which table. You have a distributed monolith.
- Skipping the operational basics. Symptom: an outage page reads "something is slow" and nobody can say which of twelve services is responsible. Centralised logs and tracing come before the split, not after.
- Too many tiny services. Symptom: a team of nine owns fourteen deployables, and onboarding takes weeks because understanding one feature means reading five repositories.
How this shows up in real projects and interviews
System design interviews reward candidates who state a default and defend it: start with a modular monolith, extract services when a specific pressure appears, and name that pressure. Saying "microservices" as an automatic answer is a weaker signal than explaining what you would give up. On real teams, the useful skill is often migrating gradually: pick the module with the clearest boundary and heaviest pain, extract it behind its existing interface, and keep the rest alone. For more on practising this style of reasoning, see the system design interview guide.
Try it yourself
Take a system you know (a past project is fine) and fill in the checklist above. Write one paragraph deciding between staying put, fixing readiness gaps and extracting one named service, and list the single measurement that would change your mind.
Show a hint
Pick the PRESSURE item with evidence behind it, such as merge conflicts in one directory or one endpoint consuming most of the CPU, rather than a hypothetical future need.
Key takeaways
- A monolith, modular monolith and microservices differ by what you deploy and who owns the data, not by how many lines of code exist.
- Each synchronous network hop adds a failure point; chained availabilities multiply, so every extra hop needs a timeout and a plan.
- Services deliver independent deploys, scaling and team autonomy only if you already have automated delivery, observability and clear domain knowledge.
- Default to a modular monolith and extract one service at a time when a concrete pressure appears.
Test yourself
A trick from this lesson's ride: Count the things you release and own data for, not the lines of code you wrote. Four more tricks like it wait along the route, each followed by a question.
Frequently asked questions
When should you use microservices instead of a monolith?
Use microservices when several teams are blocked by a shared codebase, a component needs very different scaling or release cadence, and you already have automated delivery and observability. For a small team or an unclear domain, a modular monolith is usually the safer choice.
What is a modular monolith?
It is a single deployable application whose internals are split into modules with enforced boundaries: each module exposes a small interface and owns its own data. You get simple deployment and in-process calls, and you can extract a module into a service later if needed.
Are microservices faster than a monolith?
Not by default. A call across the network is slower and less reliable than an in-process call. Microservices can improve how quickly teams ship and let you scale hot components independently, but they do not make an individual request faster.