The Hidden Cost of Microservices: The Complete Field Guide
A beginner‑friendly, book‑quality walkthrough of what “a disadvantage of microservices” actually means in practice — with the concepts, diagrams, and code you need to reason about it like an architect. Written for readers starting from zero, and built to leave you interview‑ready.
Introduction & a Short History
Splitting one giant program into many small services is easier to imagine than to run — keeping track of ten small boxes really is harder than keeping track of one big one. That difficulty is what this whole guide unpacks.
Imagine a toy box. You could keep every toy — cars, blocks, dolls, puzzles — in one giant box. It is simple to carry: one box, one lid. But when you want just the puzzle, you have to dig through everything else. And if the box splits at the bottom, every toy falls out at once.
Now imagine you keep each kind of toy in its own small box: one box for cars, one for blocks, one for puzzles. You can carry just the puzzle box to a friend’s house. If the car box breaks, your puzzles are still fine. But now you have to keep track of ten boxes instead of one, remember where each one is, and make sure they do not get lost.
That is, in plain words, the entire microservices story. The “one giant box” is called a monolith. The “many small boxes” architecture is called microservices. This tutorial is about the second half of that story — the part people forget to mention: keeping track of ten boxes is genuinely harder than keeping track of one. That difficulty is the disadvantage we are here to understand, deeply and precisely.
A little bit of history
In the 1990s and 2000s, most software was built as a monolith: one large program containing all the code — the website, the payment logic, the inventory logic, the email logic — compiled and deployed together as a single unit. This worked well when teams were small and traffic was modest.
As companies like Amazon, Netflix, and eBay grew massively in the 2000s and early 2010s, their monoliths grew too — into millions of lines of code maintained by hundreds of engineers. Deploying a single small change meant redeploying the entire application, and one bug in the “shipping label” code could crash the “checkout” feature too, because everything shared the same process and memory.
Around 2011–2014, the term “microservices” was popularised (notably by James Lewis and Martin Fowler, who wrote an influential 2014 article describing the pattern) to describe what companies like Netflix and Amazon were already doing in practice: splitting one big application into many small, independently deployable services, each owning one business capability, talking to each other over a network.
This tutorial assumes zero prior knowledge, so every new term below is explained plainly, with an everyday analogy first and a technical example second.
The Problem Microservices Tried to Solve
Before naming a disadvantage of microservices, it helps to remember the pain they replaced — the pain of the monolith. That way “microservices are complicated” reads as a trade, not a mistake.
Before we can understand a disadvantage of microservices, we must understand the disadvantage of the thing they replaced — the monolith. Otherwise “microservices are complicated” sounds like a reason never to use them, which is not true; it is a trade, not a mistake.
What is a monolith?
A monolith is like a Swiss Army knife that is one solid piece of metal. Every blade, scissor, and screwdriver is welded into a single object. It is convenient to carry — but if the metal cracks near the scissors, you may lose the whole knife, not just the scissors.
An online bookstore app where the UserService, OrderService, PaymentService, and InventoryService are all Java classes inside one Spring Boot application, compiled into one JAR file, deployed to one server (or one set of identical server copies).
Problems that appear as a monolith grows:
- Slow deployments — even a one‑line fix to the “Payments” page requires rebuilding and redeploying the whole app.
- Scaling is wasteful — if only the “search” feature is under heavy load, you still have to scale the entire monolith (including parts nobody is using right now), which wastes computing resources and money.
- One team’s bug can freeze everyone — a memory leak in “recommendations” can crash the whole process, taking down checkout too.
- Codebase becomes a “big ball of mud” — over years, everything gets tangled, and new engineers take months to become productive.
What is a microservice?
What it is: a microservice is a small, independently deployable program that owns one specific business capability (e.g., “manage orders”) and exposes that capability to other programs through a well‑defined interface, usually a network API.
Why it exists: to let each capability be built, deployed, scaled, and even rewritten separately, by a separate team, without needing to touch or even understand the other capabilities.
Where it is used: Netflix (hundreds of services), Amazon (thousands), Uber, Spotify, Airbnb, and most large‑scale modern cloud platforms.
Instead of one giant Swiss Army knife, you get a tool belt with separate tools — a screwdriver, a wrench, pliers — each independently replaceable. If the screwdriver breaks, your wrench still works fine. But now you must carry, organise, and maintain several tools instead of one.
Core Concepts You Must Know First
A short vocabulary before we go deeper — each term with what it is, why it exists, an analogy, and a small example.
1. Service boundary
What: the invisible line that decides what belongs inside a microservice and what does not.
Why: without a clear boundary, services start depending on each other’s internal details, and you lose the benefits of splitting them up in the first place.
Think of apartments in a building. Each apartment (service) has its own kitchen (database) and its own front door (API). Neighbours can knock on the door and ask for something, but they cannot just walk into your kitchen and grab food directly.
2. API (Application Programming Interface)
What: a defined set of rules describing how one program can request something from another program.
Why: it lets two services built by different teams, in possibly different languages, talk to each other reliably, without knowing each other’s internal code.
Example: the Order Service exposes GET /orders/{id}. Any other service, written in Java, Python, or Go, can call this URL and get order data back as JSON.
3. Network call (a.k.a. Remote Procedure Call)
What: sending a request over the network (Wi‑Fi, cables, the internet) to another machine or process, and waiting for a reply.
Why it matters here: a network call can be slow (milliseconds instead of nanoseconds), can fail halfway (the reply gets lost), and can time out (no answer at all) — none of which can happen with a normal function call inside one program.
Calling a function inside your own program is like talking to someone standing right next to you — instant and always heard. A network call is like sending a text message to a friend in another country: it might take a while, the message might get delayed, or your friend’s phone might be off. You have to plan for that uncertainty.
4. Bounded context
What: a term from Domain‑Driven Design (DDD) meaning “the area of the business where a specific model and vocabulary apply.” For example, the word “Customer” might mean something slightly different in the Billing service versus the Shipping service.
Why it exists: it gives teams a principled way to decide where one microservice ends and another begins — usually one microservice per bounded context.
5. Data consistency
What: the guarantee that data is correct and matches reality across the whole system, not just in one place.
Why it’s hard in microservices: each service has its own database. If Order Service marks an order “paid” but Payment Service’s database still says “pending” (because the network call between them failed), the system is now inconsistent — two “truths” disagree.
6. CAP theorem (a foundational rule for distributed systems)
What: a theorem stating that a distributed data system can only fully guarantee two of these three properties at the same time, during a network failure:
- C — Consistency: every read gets the latest write.
- A — Availability: every request gets a (non‑error) response.
- P — Partition tolerance: the system keeps working even if network messages between machines are lost or delayed.
Why it matters: since real networks do experience partitions (a switch fails, a data centre loses power), Partition tolerance is not optional in practice — so real distributed systems (which is what a microservices architecture is) must choose between Consistency and Availability when a partition happens. Monoliths mostly avoid this problem because everything lives in one place; microservices are forced to face it directly.
Two people are updating the same shared shopping list, but the phone line between them just went dead. Do you (A) let both people keep adding items and reconcile the list later, possibly with conflicts, or (B) freeze the list until the phone line is back, guaranteeing no conflicts but making the list unusable in the meantime? You must pick one. That is the CAP theorem in one sentence.
7. Latency
What: the time it takes for a request to travel and come back with a response.
Why it grows in microservices: one user action might now require five services to call each other in sequence, and each network hop adds a few milliseconds — sometimes tens of milliseconds under load.
8. Container & orchestration
What: a container (e.g., Docker) packages a service with everything it needs to run. An orchestrator (e.g., Kubernetes) automatically starts, stops, restarts, and scales many containers across many machines.
Why it exists: with dozens or hundreds of microservices, you cannot manually manage each one — you need automation.
Architecture & Components
A realistic microservices system has more moving parts than just “services.” Most of these extra components exist purely to make the many small services work together safely — in a monolith they simply do not need to exist.
| Component | What it does | Analogy |
|---|---|---|
| API Gateway | Single front door that routes each client request to the right service, and can handle auth, rate limiting, and caching centrally. | The reception desk of an office building that directs visitors to the right department. |
| Load Balancer | Spreads incoming requests across multiple copies (instances) of a service so no single instance is overwhelmed. | A restaurant host seating guests evenly across all open tables. |
| Message Queue | Lets services send messages to each other without waiting for an immediate reply (asynchronous communication). | A mailbox — you drop a letter in, and go about your day; the receiver reads it whenever they check the mailbox. |
| Service Registry / Discovery | A directory that tracks which service instances currently exist and where they live (their network address). | A phone book that updates itself every few seconds. |
| Distributed Tracing | Follows a single request as it hops across many services, so you can see the whole journey. | A tracking number that shows every warehouse a parcel passed through. |
| Config Server | Centrally stores settings (like database URLs) so services do not hardcode them. | A shared notice board instead of everyone keeping private notes that can go out of date. |
How a Single Request Actually Flows
Trace one ordinary action — “place an order” — through a microservices system, step by step, so the internal working is concrete rather than abstract.
Step by step, in plain words
1. The user taps “Buy Now” on the app.
Nothing unusual so far — the same as every other architecture.
2. The request reaches the API Gateway.
The gateway checks who the user is and forwards the request onward.
3. Order Service receives it.
Two things must be confirmed before it can say “success”: the item is in stock, and the payment goes through.
4. Order calls Inventory Service over the network and waits.
First blocking hop.
5. Order calls Payment Service over the network and waits.
Second blocking hop.
6. Only if both replies are successful does Order mark the order “confirmed” and reply back through the gateway.
The user finally sees a response.
7. Afterwards, Order publishes an event to a message queue.
The Notification Service picks it up and sends a confirmation email. This part does not need to happen before the user sees “success,” so it is done asynchronously.
What happens if the Inventory call succeeds but the Payment call times out? The stock is now reserved, but no money was charged. In a monolith, this whole sequence would typically happen inside one database transaction that either fully succeeds or fully rolls back. In microservices, there is no single transaction spanning multiple databases — this exact scenario is the heart of the disadvantage we study in Section 7.
Advantages, in Brief (So the Trade‑off Is Fair)
To keep the trade‑off honest, here are the real benefits microservices provide — these are exactly what the coming disadvantages are the price for.
- Independent deployment — teams ship their own service without waiting for others, or coordinating a “big bang” release.
- Independent scaling — scale only the “Search” service on Black Friday, not the whole system.
- Technology freedom — one team can use Java, another can use Go or Python, whatever fits their problem best.
- Fault isolation — in principle, a crash in Recommendations does not have to take down Checkout.
- Smaller, understandable codebases — a new engineer can learn one service in days, not one giant monolith in months.
- Organisational scaling — hundreds of small teams can work in parallel with minimal collision (this maps to Conway’s Law: system architecture tends to mirror team structure).
Every one of these benefits is bought by adding a network boundary between things that used to be one program. That network boundary is exactly where the cost hides — and that cost is what the rest of this tutorial unpacks in full detail.
THE Disadvantage, Unpacked in Full
If someone asks “what is a disadvantage of microservices?” — here is the single best one‑sentence answer, followed by the full explanation and its ten concrete branches.
Microservices dramatically increase distributed‑system complexity — what used to be simple, reliable, in‑memory function calls inside one program become unreliable network calls between many independent programs, which makes data consistency, testing, debugging, deployment, and operations significantly harder.
This single root cause branches out into many concrete, specific problems. Interviewers — and real production incidents — usually want the specifics, not just the headline.
7.1 — Increased operational complexity
What it is: running ten small programs is operationally harder than running one big program, even though each small program is individually simpler.
Cooking one big pot of soup on one stove is easier to watch than cooking ten small pots on ten different stoves at the same time — even though each small pot needs less attention individually, you now have to run between ten stoves.
Concretely: with one monolith, you deploy one artifact, monitor one process, and manage one set of logs. With fifty microservices, you deploy fifty artifacts (each possibly on its own schedule), monitor fifty processes, and correlate fifty separate log streams — usually across many machines and containers.
7.2 — Distributed data consistency (the hardest one)
What it is: each microservice typically owns its own private database (database‑per‑service), so there is no longer one shared database transaction that can atomically update several tables at once across service boundaries.
Imagine two separate ledgers kept by two different shopkeepers who are business partners. If shopkeeper A gives out a product but the message telling shopkeeper B to record the payment gets lost on the way, A’s ledger says “sold” while B’s ledger says “not paid.” Reconciling separate ledgers is much harder than updating one shared ledger with one pen stroke.
Example (from Section 5): Inventory Service successfully reserves stock, but the Payment Service call fails. Now stock is reserved for an order that was never paid for — an inconsistency that must be detected and fixed by extra logic (compensating actions), because there was no single database transaction wrapping both steps.
Engineers solve this with patterns like the Saga pattern (Section 15) and by accepting eventual consistency (the idea that the system will become correct eventually, a few seconds or minutes later, rather than instantly) — a mental model monolith developers rarely needed.
7.3 — Network unreliability becomes a core design concern
What it is: every inter‑service call can be slow, can time out, can partially fail (request sent but response lost), or can arrive out of order.
This is formalised in software engineering as the “Fallacies of Distributed Computing” (an old, still‑true list from Sun Microsystems engineers), including false assumptions like “the network is reliable,” “latency is zero,” and “bandwidth is infinite” — none of which are true, and all of which a microservices developer must design around, but a monolith developer usually never has to think about at all.
7.4 — Testing becomes much harder
What it is: to fully test one user action end‑to‑end, you may need several services running simultaneously and talking correctly to each other — not just one program.
Testing “place an order” now requires Order, Inventory, Payment, and Notification services all running, all correctly configured, all reachable over the network — versus a monolith where a single in‑process test can call all four modules directly with no network involved at all.
Teams address this with contract testing (each service tests that it honours an agreed “contract” with its callers, without needing every other service actually running) and service virtualisation (fake stand‑ins for other services during tests), but these are extra tools and extra discipline that a monolith simply does not need.
7.5 — Debugging and tracing a single failure is harder
What it is: when something goes wrong, the root cause might be three services away from where the symptom appeared.
In a monolith, if a light bulb does not turn on, you check one wire from the switch to the bulb. In microservices, the “wire” passes through five different rooms, each managed by a different person, and you must ask each person, one by one, “did the signal reach you?” before finding the broken segment.
This is why distributed tracing (Section 11) — tools that stitch together a request’s full journey with a shared trace ID — became essentially mandatory for microservices, whereas a monolith developer could usually just read a single stack trace.
7.6 — Increased latency from network hops
What it is: every extra network call adds delay. A user action that needed one in‑memory function call in a monolith might need five sequential network calls in microservices, each adding a few milliseconds — and under load, sometimes much more.
We quantify this properly with real numbers in Section 8.
7.7 — Deployment and infrastructure overhead
What it is: each service typically needs its own build pipeline, its own container image, its own health checks, its own scaling rules, and its own configuration — multiplied by however many services exist.
A team running 5 services multiplies almost every “one‑time” operational task by 5. A team running 300 services (realistic at Amazon‑scale companies) multiplies it by 300. This is why strong automation (CI/CD, Kubernetes) is not optional in microservices — it is a survival requirement.
7.8 — Larger security surface area
What it is: every network boundary between services is also a place where an attacker could try to intercept, impersonate, or inject bad data — something that does not exist inside a single process’s memory space.
Full detail in Section 10.
7.9 — Organisational & cognitive overhead
What it is: engineers must now understand not just “my service” but also which other services it depends on, what their contracts are, and how failures propagate — a wider mental model than “just my module” in a monolith.
7.10 — Higher infrastructure cost
What it is: running many small services often means many small servers or containers, each with baseline overhead (its own runtime, its own memory footprint, its own monitoring agent), which can add up to more total resource usage than one efficiently‑packed monolith, especially at smaller scale.
None of the above problems are bugs in microservices — they are the microservices architecture’s very well‑documented, unavoidable cost of splitting one program into many. The skill of a good architect is not “avoiding” these costs, but knowing when the benefits (Section 6) are worth paying them, and applying the right patterns (Section 15) to keep the cost manageable.
Performance & Scalability Trade‑offs
Microservices scale specific hot paths well, but each network hop is a serialisation step, a network trip, and a deserialisation step — work that simply does not exist in an in‑process call.
Scalability upside: you can scale exactly the service under load — e.g., during a flash sale, scale only Product Service and Order Service, not Auth or Notifications, saving cost and giving faster, more targeted scaling.
Performance downside: latency accumulates across hops. Consider this rough (illustrative) comparison:
| Architecture | Typical path for “place order” | Approx. added latency |
|---|---|---|
| Monolith | 1 in‑process function call chain | ~0.1–1 ms (memory‑access speed) |
| Microservices | 4–6 sequential network calls (gateway → order → inventory → payment → notify) | ~20–150 ms depending on network, serialisation, and load |
These numbers are illustrative, not universal — real numbers vary hugely by network quality, service load, and whether calls run in parallel or sequence. The lesson is structural: each network hop is a serialisation step (turning objects into JSON/bytes), a network trip, and a deserialisation step — work that simply does not exist in a function call within one process.
How teams mitigate this
- Parallelise independent calls instead of calling services one after another when they do not depend on each other.
- Caching — store frequently‑read data (like product details) close to where it is needed, so not every request needs a fresh network round trip (Section 13).
- Asynchronous / event‑driven calls — do not make the user wait for steps that can happen in the background (like sending an email).
- gRPC or binary protocols instead of verbose JSON‑over‑HTTP, to cut serialisation time in high‑throughput internal calls.
High Availability & Failure Behaviour
One slow service can drag its neighbours down with it — unless you defend against that possibility explicitly with circuit breakers, timeouts, retries, and bulkheads.
Cascading failure
What it is: when one slow or broken service causes the services that depend on it to also become slow or broken, spreading like a chain reaction.
One slow cashier in a supermarket causes a queue to build up, which blocks the aisle, which then slows down other shoppers trying to reach different cashiers entirely. One slow point can jam up the whole store.
Circuit breaker pattern (the standard fix)
What it is: a safety mechanism that “trips” (like an electrical circuit breaker in your home) and stops calling a failing service for a while, instead immediately returning a fallback response, giving the failing service time to recover instead of being hit with more requests.
CircuitBreakerConfig config = CircuitBreakerConfig.custom()
.failureRateThreshold(50) // trip if 50% of calls fail
.waitDurationInOpenState(Duration.ofSeconds(10)) // pause 10s before retrying
.slidingWindowSize(20) // look at the last 20 calls
.build();
CircuitBreaker breaker = CircuitBreaker.of("inventoryService", config);
Supplier<StockStatus> decorated = CircuitBreaker
.decorateSupplier(breaker, () -> inventoryClient.checkStock(itemId));
StockStatus result = Try.ofSupplier(decorated)
.recover(throwable -> StockStatus.unknownFallback())
.get();
Here, if the Inventory Service starts failing too often, the circuit “opens” and further calls immediately return a safe fallback (unknownFallback()) instead of hammering an already‑struggling service — protecting both services and giving the system a chance to recover instead of collapsing entirely.
Retries, timeouts, and bulkheads
- Timeout: give up waiting after a fixed time instead of waiting forever.
- Retry (with backoff): try again after a short, increasing delay — but only for safe, “idempotent” operations (ones that do not cause harm if repeated).
- Bulkhead: limit how many concurrent calls can go to one dependency, so one slow dependency cannot consume all available threads or connections (named after ship compartments that stop one flooded section from sinking the whole ship).
Security Surface
Every network boundary is a new door — and every new door needs its own lock. Microservices multiply the number of doors, which multiplies the security work.
In a monolith, most communication happens inside one process’s memory — invisible to the outside network. In microservices, communication happens over the network between services, even inside your own data centre (“east‑west traffic”), which introduces new attack surfaces:
- Service‑to‑service authentication — you must verify that a request really came from the legitimate Order Service, not an impostor, using things like mutual TLS (mTLS) or short‑lived tokens.
- More network endpoints to secure — each service exposes its own API, meaning more doors that must each be locked properly.
- Secrets sprawl — database passwords, API keys, and certificates now need to be managed and rotated across many services instead of one config file.
- Harder‑to‑audit access — tracking “who can call what” across dozens of services is more complex than one access‑control list in one app.
Modern mitigation: a service mesh (like Istio or Linkerd) can automatically enforce mTLS and access policy between services, so individual developers do not need to hand‑roll security in every service.
Monitoring, Logging & Tracing
Because a single user action can touch many services, monitoring must evolve to follow requests across them — traces, centralised logs, and metrics stitched together in one place.
Distributed tracing
What it is: a system that tags every request with a unique trace ID at the very first entry point, and every service passes that same ID forward and records its own timing under it — so you can later see the entire journey of one request across every service it touched.
OpenTelemetry (the current industry‑standard instrumentation layer), Jaeger, and Zipkin are common choices for collecting and visualising traces.
Centralised logging
What it is: instead of reading log files on fifty different machines, all services ship their logs to one central place (e.g., an ELK stack — Elasticsearch, Logstash, Kibana — or a hosted alternative) so you can search across everything at once.
Metrics & dashboards
What it is: numeric measurements over time — request rate, error rate, latency percentiles (like “95% of requests finish under 200 ms”) — collected per service and displayed on dashboards (commonly Prometheus for collection, Grafana for visualisation).
Deployment & Cloud Operations
Once you are past a handful of services, container orchestration is close to mandatory — and blue‑green, canary, and rolling deployments become the everyday tools for shipping safely.
What changes: instead of one deployment pipeline, you typically need one pipeline per service (though templated / shared pipelines reduce duplication). Container orchestration (typically Kubernetes) becomes close to mandatory once you are past a handful of services, because manually starting, restarting, and scaling dozens of processes by hand is not sustainable.
Common deployment strategies used to reduce risk
- Blue‑green deployment: run the old version (“blue”) and new version (“green”) side by side, then switch traffic over instantly, keeping the old version ready as an instant rollback.
- Canary deployment: send a small percentage (e.g., 5%) of real traffic to the new version first, watch for errors, then gradually increase.
- Rolling update: replace old instances with new ones a few at a time, so the service never goes fully offline.
Cost of this convenience: all of this — orchestration, pipelines, canary tooling, service meshes — is infrastructure a monolith usually does not need at all. It is powerful, but it is also a genuine, ongoing operational and learning cost.
Databases, Caching & Load Balancing
The database‑per‑service rule is what keeps services truly independent — and losing cross‑table SQL joins is a direct, unavoidable consequence of that rule.
Database‑per‑service
What it is: best practice says each microservice should own its own private database, which no other service is allowed to query directly.
Why: it keeps services truly independent — a schema change in Order Service’s database cannot accidentally break Payment Service, because Payment Service was never allowed to touch Order’s tables directly.
The cost: no more simple cross‑table SQL joins across, say, Orders and Payments. If you need combined data, you must fetch it via API calls (slower) or maintain a separate read‑optimised copy (more complexity) — this is a direct, unavoidable consequence of splitting the data, not just the code.
Caching
What it is: temporarily storing a copy of frequently‑needed data somewhere fast to access (often in memory, using tools like Redis), so you do not repeat an expensive database query or network call every single time.
Instead of walking to the library every time you want to check a fact, you write it on a sticky note on your desk after the first lookup. Next time, you just glance at the sticky note.
New problem this introduces: cache invalidation — knowing when the sticky note is out of date and needs updating. This is famously one of the two hardest problems in computer science (the joke being: “there are only two hard things in computer science: cache invalidation, naming things, and off‑by‑one errors”).
Load balancing
What it is: distributing incoming requests across multiple running copies (instances) of the same service, so no single instance gets overwhelmed.
Common algorithms:
- Round robin: send requests to each instance in turn, like dealing cards one at a time.
- Least connections: send the next request to whichever instance currently has the fewest active requests.
- Weighted / latency‑based: send more traffic to faster or more powerful instances.
APIs Between Microservices
Microservices are only as good as the contracts between them. Two practical challenges — versioning and sync vs. async — dominate real production work.
Versioning
What it is: when Order Service changes its API (e.g., renames a field), every other service calling it must be updated too — or the old version must be kept running alongside the new one until every caller has migrated.
@RestController
@RequestMapping("/api/v2/orders")
public class OrderControllerV2 {
@GetMapping("/{id}")
public ResponseEntity<OrderResponseV2> getOrder(@PathVariable String id) {
Order order = orderService.findById(id);
// v2 adds a new "estimatedDelivery" field without breaking v1 clients
return ResponseEntity.ok(OrderResponseV2.from(order));
}
}
Keeping /api/v1/orders alive alongside /api/v2/orders lets old clients keep working while new clients adopt the new shape — but now you are maintaining two versions of the same logic, which is more work than maintaining one.
Synchronous vs. asynchronous communication
| Style | What it means | Trade‑off |
|---|---|---|
| Synchronous (e.g., REST, gRPC) | Caller sends a request and waits for a reply before continuing. | Simple to reason about, but couples services’ availability together — if the callee is down, the caller is stuck. |
| Asynchronous (e.g., message queues, events) | Caller sends a message and moves on; the receiver processes it whenever it can. | Services stay decoupled and resilient to each other’s downtime, but the caller can no longer assume the action is “done” immediately — it introduces eventual consistency. |
Design Patterns & Anti‑patterns
Sagas, API gateways, circuit breakers, and the Strangler Fig migration — the seatbelts that make microservices survivable — alongside the anti‑patterns (distributed monolith, chatty services, shared database) that quietly undo them.
Pattern: Saga
What it is: a way to manage a multi‑step business transaction that spans several services, where each step has a matching “undo” step (a compensating action), used instead of one shared database transaction.
1) Reserve stock → 2) Charge payment → 3) Confirm order. If step 2 fails, run the compensating action for step 1: release the reserved stock. The system reaches a consistent end state even though there was never one big atomic transaction.
public class PlaceOrderSaga {
public void execute(OrderRequest request) {
boolean stockReserved = false;
try {
inventoryClient.reserveStock(request.getItemId(), request.getQty());
stockReserved = true;
paymentClient.charge(request.getUserId(), request.getAmount());
orderRepository.markConfirmed(request.getOrderId());
} catch (PaymentFailedException e) {
if (stockReserved) {
inventoryClient.releaseStock(request.getItemId(), request.getQty());
}
orderRepository.markFailed(request.getOrderId());
}
}
}
Pattern: API Gateway
Covered in Section 4 — a single, controlled entry point that hides internal service topology from clients.
Pattern: Circuit Breaker & Bulkhead
Covered in Section 9 — protects the system from cascading failure.
Pattern: Strangler Fig (migration pattern)
What it is: a gradual way to migrate a monolith into microservices by slowly routing individual features to new services one at a time, while the old monolith still handles everything else, until eventually nothing is left in the monolith (named after a vine that slowly grows around a tree until the original tree is gone).
Anti‑pattern: The “Distributed Monolith”
This is what happens when teams split code into many services but still deploy them all together and keep them tightly coupled (e.g., one shared database, or services that must always be deployed in a specific order together). The result is the worst of both worlds: all the network latency and operational complexity of microservices, with none of the independent‑deployment benefit that was the whole point.
Anti‑pattern: Chatty services
What it is: splitting things so finely that completing one simple action requires dozens of tiny network calls back and forth between services, multiplying latency for no real benefit.
Anti‑pattern: Shared database
What it is: multiple “independent” services secretly reading and writing the same database tables. This silently re‑creates monolith‑style coupling (a schema change breaks multiple services) while still paying the network cost of separate deployments.
Best Practices & Common Mistakes
Seven habits that keep the microservices tax manageable — and six mistakes that quietly turn a microservices project into a distributed monolith.
Best Practices
- Start with a monolith when the team or product is small, and split into microservices only when you have a concrete, painful reason (a well‑known industry heuristic, sometimes called “monolith‑first”).
- Design service boundaries around business capabilities (bounded contexts), not arbitrary technical layers.
- Invest early in automation: CI/CD pipelines, container orchestration, and infrastructure‑as‑code, because manual operations do not scale past a handful of services.
- Invest early in observability: tracing, centralised logs, and metrics — retrofitting these onto fifty already‑running services is much harder than building them in from service one.
- Prefer asynchronous communication for steps that do not need an immediate answer, to reduce tight coupling and cascading‑failure risk.
- Use contract testing so services can be tested independently while still trusting they will work together correctly.
- Apply circuit breakers, timeouts, and retries with backoff on every network call by default, not as an afterthought.
Common Mistakes
- Splitting into microservices “because it’s trendy,” before the team even understands its own domain boundaries well.
- Making services too small (“nano‑services”), causing excessive chattiness and overhead.
- Sharing one database across “independent” services (the shared‑database anti‑pattern).
- Skipping distributed tracing until after the first painful multi‑hour outage that nobody could diagnose.
- Assuming network calls always succeed quickly — not adding timeouts, leading to threads piling up and cascading failure.
- Underestimating the organisational cost — microservices require strong DevOps maturity; without it, they often cause more outages than the monolith they replaced.
Real‑World & Industry Examples
Companies that made microservices work at huge scale — and companies that publicly moved specific workloads back towards simpler designs after finding the tax too high for that particular problem.
Pioneer at scale
Netflix is often cited as a pioneer of microservices at scale, running hundreds of services to support global streaming. To manage the resulting complexity, Netflix built (and open‑sourced) tools like Hystrix (an early circuit‑breaker library) and Eureka (service discovery) — direct evidence that operating at this scale requires purpose‑built tooling to survive the complexity described in Section 7.
Service‑oriented at scale
Amazon famously moved from a large monolithic application to a service‑oriented, then microservices‑based architecture in the 2000s to let thousands of engineers ship independently. The trade‑off: an enormous internal investment in tooling, service ownership rules, and operational standards to keep thousands of services coherent.
Thousands of services
Uber grew to thousands of microservices supporting rides, payments, and maps across cities. Uber has also publicly discussed challenges from this scale — including the difficulty of understanding service dependencies and the overhead of managing so many independent codebases — leading them to invest heavily in internal developer platforms and, in some cases, consolidate overly‑fragmented services.
Not always further
Not every large company keeps pushing further into microservices forever. Some engineering teams have publicly described moving specific workloads back towards simpler, more monolithic designs after finding that excessive service fragmentation increased cost and operational overhead for that particular workload, without proportional benefit. The lesson is not “microservices are bad” — it is that the disadvantages covered in this tutorial are real enough that even sophisticated engineering organisations sometimes reverse course for specific pieces of their systems.
Frequently Asked Questions
The questions people ask most about microservices disadvantages — plus a few interview‑style bonuses for readers preparing for system‑design rounds.
Is the main disadvantage of microservices “complexity,” or is that too vague?
“Complexity” is the honest one‑word summary, but a strong answer names where that complexity shows up: distributed data consistency, network latency and failure handling, testing across services, debugging across services, and operational / deployment overhead. Naming the specifics (Section 7) is what separates a memorised answer from an understood one.
Do microservices always perform worse than a monolith?
Not necessarily overall — microservices can scale specific hot paths better under real production load. But for a single request, extra network hops typically add latency compared to in‑memory calls in a monolith. It is a trade: worse per‑request latency, potentially better overall scalability and team velocity.
What is “eventual consistency” and why do microservices need it?
It means the system will become fully correct and consistent “eventually” (often within seconds), rather than instantly, because updates to different services’ databases cannot happen in one atomic transaction. Microservices need it because splitting the database is a direct consequence of splitting the services.
Is a service mesh mandatory for microservices?
No — it is optional tooling that becomes more valuable as the number of services grows, because it automates cross‑cutting concerns like mTLS security, retries, and traffic control, instead of every team re‑implementing them individually.
How many microservices should a small team start with?
Many experienced architects suggest starting with a well‑structured monolith and splitting out services only when you hit a concrete, specific pain point (like one module needing very different scaling) — not splitting upfront just because microservices are popular.
What is the difference between a microservice and a “distributed monolith”?
A true microservice can be deployed independently without coordinating with other services. A “distributed monolith” looks like separate services on paper, but they must be deployed together or share a database — meaning you pay all the network cost of microservices with none of the independent‑deployment benefit.
Bonus: interview‑style questions
Name three concrete disadvantages of microservices, not the headline. Beginner
Distributed data consistency (no cross‑service transactions), higher latency from network hops, and harder debugging because a single request now spans many services and log files.
How does the Saga pattern replace a cross‑service database transaction? Intermediate
Each service performs its own local transaction and publishes an event when it completes. If a later step fails, a compensating action for each previously completed step is executed in reverse order, so the system ends in a consistent state even though no cross‑service lock ever existed.
Where does CAP theorem force microservices to make a hard choice? Intermediate
During a network partition, a distributed system cannot be both fully consistent and fully available. Microservices are distributed systems by construction, so they must explicitly choose — most business flows favour availability with eventual consistency, but money movements and inventory writes often favour consistency.
How would you defend a chain of services from a slow dependency? Advanced
Timeouts on every outbound call, retries with exponential backoff only for idempotent operations, circuit breakers to stop hitting a failing dependency, and bulkheads (isolated thread and connection pools) so a slow dependency cannot consume every thread in the caller.
Why is a shared database across microservices a red flag? Advanced
Because it silently reintroduces the tightest form of coupling — the data layer — even after the code layer has been split. A schema change ripples across every team using the tables, and every service’s bugs or breaches touch everyone else’s data. You pay all of the network cost of microservices with none of the independent‑deployment benefit.
Summary & Key Takeaways
Microservices are not a mistake to avoid; they are a trade to make consciously. The seven ideas below are the shortest honest answer to “what is a disadvantage of microservices?”
Remember This
- Microservices split one big program into many small, independently deployable ones, each owning a specific business capability.
- The core cost of this split: what used to be fast, reliable, in‑memory function calls become slower, less reliable network calls.
- That one shift causes almost every named disadvantage: harder data consistency, higher latency, harder testing, harder debugging, heavier deployment and operational overhead, larger security surface, and higher organisational cost.
- CAP theorem explains why distributed systems (which microservices are) must trade consistency against availability during network partitions — a trade‑off monoliths mostly avoid.
- Patterns like circuit breakers, sagas, API gateways, and distributed tracing exist specifically to manage this cost — they are the “seatbelts” for a system built this way.
- Real companies (Netflix, Amazon, Uber) prove microservices can work at huge scale, but only with equally huge investment in tooling, automation, and observability.
- The disadvantage is not a flaw to be “fixed” — it is a genuine trade‑off. Good architecture means consciously deciding the benefits (Section 6) are worth this cost, for your specific team and product, not following it as a trend.
The systems that hold up over years are almost always the ones whose engineers treated microservices as a trade, not as a badge — deciding in each case whether the independent deployment, independent scaling, and organisational autonomy were worth the well‑documented tax of distributed complexity. The disadvantages in this guide are not warnings against microservices; they are the honest ticket price for the seat.