Practice this topic in a realistic system design interview
Software feels simpler when everything runs on one machine. The code can call functions directly. The data is in one place. The clock is the same for every part of the program.
Now spread that same system across many machines connected by a network. Suddenly, many of those simple assumptions stop being true.
That is the world of distributed systems: work is split across multiple machines, and those machines talk to each other over a network.
A request can time out even though the server completed it. Two nodes can disagree about the current value of the same record. A clock can be correct enough for logs and still wrong enough to corrupt ordering logic.
These are not rare edge cases. They are normal production problems.
The goal of this chapter is to build the right instincts:
Distribution is expensive in complexity. Do it for a clear reason, not because it sounds modern.
| Reason | What It Solves | Common Example |
|---|---|---|
| Scale | One machine runs out of CPU, memory, disk, or network capacity | Add application servers behind a load balancer |
| Availability | One failed machine should not take down the whole service | Run replicas across multiple availability zones |
| Latency | Users are far from the only data center | Serve traffic from a region closer to the user |
| Isolation | One component should not exhaust resources for everything else | Split background jobs from request handling |
| Team autonomy | Large teams need independent ownership and deployments | Separate services for payments, search, and notifications |
| Data residency | Some data must stay in a specific region or country | Store EU customer data in EU regions |
The diagram below shows the basic shift. A single machine becomes several services, replicas, and network calls.
The benefits are real. So is the cost. Every extra machine, service, replica, queue, and region creates another place where data can get out of sync and another network call that can fail.
In a single process, failures are usually easier to understand. A function returns, throws an error, or the process stops. The boundary is fairly clear.
Distributed systems introduce partial failure: one part of the system can fail while the rest continues running.
For example:
Partial failure is hard because the system is not clearly up or down. Some parts work. Some parts do not. The result is often unknown.
Consider a checkout service that asks the payment service to charge a card.
After the timeout, checkout does not know what happened:
This is why distributed systems use several patterns together. There is no single magic fix.
Timeouts stop callers from waiting forever. Retries help with short-lived failures.
Idempotency keys let the receiver recognize "this is the same operation again," so a retry does not charge a card twice. Durable state lets recovery continue from a known point. Reconciliation jobs fix mismatches later, once the system has more facts.
The important lesson: a timeout does not mean failure. It means the caller stopped waiting.
Distributed systems communicate by sending messages over a network. Those messages pass through network cards, switches, routers, load balancers, proxies, operating systems, queues, and application code. Any of those layers can delay, drop, duplicate, or reorder traffic.
A request or response can be lost. It can also arrive much later than expected. A retry can deliver the same operation twice. Two messages sent in order can arrive out of order.
Some nodes may stay connected to each other but get cut off from the rest of the system. In the trickiest case, A can reach B, but B's responses to A keep disappearing.
The Two Generals Problem is a classic way to explain why unreliable communication creates uncertainty.
Two generals must attack at the same time. They can only communicate by sending messengers through dangerous territory. Any messenger might be captured.
No fixed number of confirmations can make both sides perfectly certain. Each confirmation creates a new question: "Did the other side receive my confirmation?"
Real systems are not helpless. TCP, retries, confirmations, message brokers, replication logs, and consensus protocols all help under specific assumptions.
But they do not remove uncertainty completely. They turn it into engineering choices with trade-offs: more latency, duplicate handling, storage, coordination, and recovery work.
When a request crosses a network, assume the caller may not know the final outcome.
Good distributed designs make remote operations idempotent, so retries are safer. They use timeouts, so resources are not held forever. They also produce useful logs, metrics, and traces, so engineers can understand what happened during failures.
They also make operations recoverable. If work stops halfway through, another process can finish it or undo the right parts later.
Teams should also be honest about delivery behavior:
The common mistake is treating a remote call like a local function call. It is not. A remote call asks another independent system to do work. It may succeed, fail, or leave you unsure.
On one machine, timestamps often feel reliable enough to order events. In a distributed system, every machine has its own clock. Those clocks drift, get adjusted, and may disagree by milliseconds or more.
NTP and similar systems help keep clocks close enough for logs, monitoring, token expiration, and human debugging. They are not enough to safely order every important event in a distributed system.
Those differences look small. In a busy system, thousands of events can happen during that tiny window.
Consider two users editing the same profile.
The update with the larger timestamp wins, but that may not be the update that truly happened later. Worse, the other update may disappear without anyone noticing.
The same problem shows up in many places. "Last write wins" can drop valid writes. Distributed locks can become unsafe when machines disagree about the current time.
Cache expiration can behave strangely under clock skew. Incident timelines can also look out of order when logs come from machines that never fully agreed on "now."
If the system only needs approximate time, wall clocks are fine. If the system must order events safely, use something stronger.
Monotonic clocks measure elapsed time on one machine, but you cannot compare them across machines. Lamport timestamps track cause-and-effect across message exchanges, but they cannot detect every case where two events happened at the same time.
Vector clocks can detect concurrent events, but they carry extra metadata that grows as the system grows. Hybrid logical clocks combine physical time with logical ordering, which is useful but more complex to implement.
For the strongest guarantee, a consensus log gives the system one agreed order. The cost is extra coordination and latency.
The rule of thumb: use wall-clock time for observation and expiration; be careful using it for rules that must be correct.
Network calls do not have stable timing. A request that usually takes 2 ms may sometimes take 200 ms or 2 seconds. Under overload, a service may still be alive but too slow to be useful.
Latency is usually described with percentiles:
Average latency hides the problem. Users and upstream services feel the slow requests at the tail.
A user request often fans out, meaning it calls several other services before it can return a response.
If one downstream call has a 1% chance of being slow, four independent calls have about a 3.9% chance that at least one is slow:
The more services you call, the more likely one of them is slow.
The causes are familiar to anyone who has debugged a slow endpoint: network congestion, packet retries, queueing inside a service, garbage collection, or runtime pauses.
Outside the application, CPU throttling, slow disk or database queries, cold caches after a restart, and long distances between regions can all add slow requests.
Latency problems become reliability problems when callers wait too long. While they wait, they hold threads, connections, memory, or queue space.
The defenses are practical:
Also avoid unnecessary fan-out on critical paths, and track tail latency, not only averages.
The objective is to keep slow requests from dragging the whole system down, not to make every request fast.
On a single machine, state usually has one obvious location. In a distributed system, state is copied, split, cached, queued, and replicated across many places.
There is no instant, always-correct view of "the whole system" that every node can read for free. To get that kind of view, nodes must coordinate, which means extra communication and waiting.
Different clients may see different values depending on which replica, cache, shard, or region handles the request.
A user updates their address, but another service reads the old value from a lagging replica. Two services read the same counter, update it separately, and one write overwrites the other.
A retry creates two orders or sends two emails. Two regions accept different updates for the same record during a network partition. The database is updated, but a cache still serves the old value.
These problems are not always unacceptable. A stale product recommendation is usually fine. A stale bank balance during a withdrawal is not.
The right design depends on the business invariant. An invariant is a rule the system must never break, such as "do not charge a customer twice" or "do not sell more seats than exist."
Keeping distributed state consistent requires coordination. Coordination means nodes talk to each other before deciding what is safe to do.
That can involve extra messages, waiting for replicas, leader election, locks, transactions, quorums, or consensus.
That cost shows up as:
This is the heart of many distributed-system trade-offs. Stronger guarantees make application behavior easier to reason about, but they require more coordination. Weaker guarantees are faster and more available, but the application must tolerate or repair temporary mismatches.
During a network partition, a system often has to choose between:
Neither choice is always correct. A payment ledger, a social feed, and an online presence indicator should not make the same trade-off.
The fallacies of distributed computing are a useful checklist of assumptions that stop being safe once software crosses a network.
| Assumption | Reality |
|---|---|
| The network is reliable | Messages can be lost, delayed, duplicated, or reordered |
| Latency is zero | Every remote call has a cost |
| Bandwidth has no limit | Large payloads and high fan-out can fill up network links |
| The network is secure | Traffic needs authentication, authorization, and encryption |
| Network layout does not change | Nodes, routes, and dependencies change over time |
| There is one administrator | Different teams and vendors control different parts |
| Moving data is free | Moving data costs money, CPU, and time |
| The network is the same everywhere | Regions, devices, protocols, and links behave differently |
These fallacies are old because the problems are old. They still matter because cloud platforms make distributed systems easier to create, not easier to reason about.
Distributed systems require a different way of thinking. The main change is this: stop asking only "did it work?" and start asking "what can I safely know from here?"
| Single-Machine Thinking | Distributed-System Thinking |
|---|---|
| A call succeeds or fails | A call may leave the outcome unknown |
| State has one current value | State may differ across replicas |
| Time can order events | Clocks may disagree |
| Failure is usually local and obvious | Failure can be partial and unclear |
| Retrying is simple | Retrying can duplicate side effects |
| Strong coordination is cheap | Coordination costs latency and availability |
When designing a distributed operation, ask:
The last question matters most. You do not need perfect consistency everywhere. You need to know which rules must never be broken.
Distribution helps systems scale, survive failures, reduce latency, and support independent ownership. It is difficult because it removes the simple assumptions of single-machine software.
The biggest shift is partial failure: some components can fail while others keep running.
Networks are unreliable, so messages can be lost, delayed, duplicated, reordered, or cut off by partitions. There is no shared clock, so wall-clock timestamps are risky for ordering important events across machines.
Latency is unpredictable. A slow dependency can become a reliability problem for the whole system. There is also no free global state, so different nodes can hold different views at the same time.
Good distributed systems do not pretend these problems disappear. They make uncertainty visible and manageable with timeouts, retries, idempotency, durable state, observability, reconciliation, and carefully chosen consistency guarantees.
10 quizzes