Lessons
1 · System Design Foundations
2 · APIs and Communication
3 · Capacity Estimation
4 · Core Building Blocks
5 · Databases
- 12Relational Databases and Data Modeling for System Design
- 13Database Indexes and Query Performance: How to Make Reads Fast
- 14Transactions, Isolation Levels and Concurrency Control: Preventing Race Conditions
- 15Database Replication: Primary-Replica, Read Replicas, Lag and Failover
- 16Database Sharding and Partitioning: How It Works and When You Need It
- 17SQL vs NoSQL: Data Models and How to Choose the Right Database
6 · Distributed Systems
- 18Why Distributed Systems Are Hard: Partial Failure, Timeouts and Clocks
- 19CAP Theorem and Consistency Models Explained Without the Myths
- 20Idempotency, Retries and Message Delivery Guarantees
- 21Distributed Transactions: Two-Phase Commit, the Outbox Pattern and Sagas
- 22Distributed Locks, Leases, Leader Election and Consensus
7 · Reliability and Operations
8 · Architecture and Security
9 · Interview Method and Foundation Case Studies
10 · Real-World Case Studies
- 33Design a Notification System: Email, SMS and Push at Scale
- 34Design a Chat System Like a Messaging App
- 35Design a News Feed: Fan-Out, Ranking and the Celebrity Problem
- 36Design Search Autocomplete (Typeahead)
- 37Design a Distributed Job Scheduler
- 38Design a Ticket Booking System Without Overselling
- 39Design a Payment System: Idempotency, Ledgers and Reconciliation
11 · Large-Scale Case Studies
- 40Design a Video Streaming Platform: Upload, Transcoding and Adaptive Streaming
- 41Design a Ride-Hailing System: Location, Matching and Real-Time Updates
- 42Design a Food Delivery Platform: Orders, Restaurants and Delivery Partners
- 43Design an E-Commerce System: Catalog, Cart, Checkout, Inventory and Flash Sales
- 44Design a Web Search Engine: Crawling, Inverted Indexes and Ranking
12 · Interview Readiness
Lesson 18 · Distributed Systems
Why Distributed Systems Are Hard: Partial Failure, Timeouts and Clocks
What changes when work is spread over many machines: partial failure, messages that are lost, delayed, duplicated or reordered, timeouts with unknown outcomes, process pauses, and clocks that disagree. Learn why a timeout does not tell you what happened, why wall clocks cannot order events, how logical clocks help, and the design habits that follow.
For
Interview candidates building SDE-2 depth, and developers whose systems span several services, databases and queues.
Level
Before you start
Read Synchronous vs Asynchronous Communication (article 05), Message Queues (article 11) and Database Replication (article 15). They show the components; this article explains why connecting them is hard.
On this page
Key takeaways
- In a distributed system, parts fail independently: some machines and links work while others do not, and you often cannot tell which.
- A timeout has three possible meanings (it failed, it succeeded but the reply was lost, it is still running), so every retried operation must be safe to repeat.
- Messages can be lost, delayed, duplicated and reordered; design consumers and protocols that tolerate all four.
- Wall clocks on different machines disagree and can jump, so do not use them to order events across machines; use sequence numbers, versions or logical clocks.
- A process can pause for seconds without knowing it, so time-based leases and locks need fencing.
Your service calls the payment provider to charge a customer ₹4,999. After two seconds, the call times out. What happened?
Maybe the request never reached the provider. Maybe it reached the provider, the card was charged, and the response was lost on the way back. Maybe the provider is still processing it and will charge the card in three seconds. From your side, all three look exactly the same: no response. If you retry, you might charge twice. If you do not, you might never charge at all.
On one machine, a function call either returns or throws. Across a network, there is a third outcome, "I don't know", and it changes everything. This article explains the handful of facts that make distributed systems hard, so that the techniques in the rest of Module 6 (consistency models, idempotency, sagas, locks) make sense as answers to real problems.
What you will learn
#- What makes a system "distributed", and why you already have one.
- Partial failure, and why it is different from a crash on one machine.
- What networks do to messages: loss, delay, duplication and reordering.
- Why a timeout cannot tell you what happened, and how to choose timeouts.
- Process pauses and why a node can be wrong about its own state.
- Why clocks disagree, why wall-clock time cannot order events across machines, and how logical clocks help.
- Design principles that follow, used throughout the rest of the course.
You already have a distributed system
#A distributed system is a set of programs on different machines that cooperate by sending messages over a network. Pixly, as designed so far, already is one: clients, load balancers, a fleet of API servers, a cache cluster, a primary database with replicas, a queue and workers, object storage, a CDN, and third-party email and payment providers. Every arrow in the diagram is a network message that can go wrong.
Amazon's Builders' Library has an article on exactly this topic, arguing that the hard part of distributed programming is that every network interaction has many more ways to fail than a local call, and that engineers must handle each of them (Challenges with distributed systems).
Partial failure
#On a single computer, failure tends to be total: if the machine crashes, everything on it stops together. In a distributed system, parts fail independently:
- one of eighteen API servers runs out of memory;
- the network link between two availability zones drops packets;
- the cache is healthy but slow because one node is swapping;
- the database primary is fine, but one replica is ten seconds behind;
- the email provider accepts requests but delivers them an hour late.
This is partial failure, and it is the defining difficulty. The system is neither up nor down; it is partly working, in ways that are hard to observe and that change from moment to moment. Code written for "it works or it throws" is not ready for "it half works, sometimes, slowly".
What the network does to messages
#A network between two machines can:
Behaviour | Example | Consequence if ignored |
|---|---|---|
Lose a message | A packet dropped; a connection reset | Work never happens; the caller waits |
Delay a message | Congestion, a slow route, a retransmission | Timeouts fire; stale data arrives late |
Duplicate a message | A retry after a lost acknowledgement | The same work runs twice |
Reorder messages | Two messages take different paths, or a retry overtakes | Updates applied in the wrong order |
Partition the system | A switch fails; two groups of servers cannot reach each other | Each side may act alone (split brain, article 15) |
TCP hides some of this inside one connection (it resends lost packets and keeps order), but not across connections, retries, queues or services. At the application level, assume all five happen.
There is also a deeper limit. Two parties communicating over a link that can lose messages cannot be certain they agree: every confirmation might itself be lost, and so might the confirmation of the confirmation. This is often explained as the "two generals" problem. Distributed systems therefore never achieve certainty by sending more messages; they use designs that stay correct when messages are lost or repeated.
Timeouts: the three outcomes
#When a request gets no reply within a deadline, you know only that you did not hear back. The possibilities:
1. The request was lost -> nothing happened2. The work happened, but the reply was lost -> it DID happen3. The work is still happening (slow server) -> it MAY happen laterYou cannot tell these apart from the caller's side. This one fact drives much of distributed-systems design:
- Retrying is necessary (case 1 is common), but dangerous (cases 2 and 3 make retries duplicates). Retries are only safe for idempotent operations, which is why article 20 is about idempotency.
- "Did my request succeed?" must be answerable later, for example by querying status with an idempotency key or request id.
- The callee must tolerate duplicates, because callers will retry.
Choosing a timeout
#Too short, and healthy-but-slow requests are cut off and retried, adding load exactly when the system is slow. Too long, and threads and connections are held hostage by a dead dependency (Little's Law, article 02).
A sound approach, described in the Amazon Builders' Library article on timeouts and retries, is to base the timeout on the downstream service's latency distribution: pick an acceptable rate of false timeouts (for example 0.1%) and set the timeout near the matching latency percentile (for example p99.9) (Timeouts, retries and backoff with jitter). Then check that your own overall deadline still meets your latency requirement: a request that calls three services in sequence cannot give each one a 2-second timeout and still promise a 1-second response.
Two more practical habits:
- Propagate deadlines. If the user's request has 800 ms left, a downstream call should not wait 2 seconds. Pass the remaining budget along.
- Separate connection and request timeouts. Failing to connect is usually quick to detect; a stuck response is not.
Process pauses: when a node is wrong about itself
#A program can stop running for a while without knowing it:
- a garbage-collection pause in a managed runtime such as the JVM;
- the virtual machine being paused or migrated by the hypervisor;
- the operating system swapping memory to disk;
- a laptop lid closing on a developer's machine running a "production" script.
When the program resumes, it continues from where it was, believing almost no time has passed. If it held a lock that expired during the pause, or was the leader and another node was elected meanwhile, it will now act on authority it no longer has. Martin Kleppmann's analysis of distributed locking walks through exactly this scenario, with long garbage-collection pauses, and shows why locks for correctness need fencing tokens that let storage reject stale holders (How to do distributed locking). Article 22 builds on this.
The general lesson: a node cannot fully trust its own sense of time or status. Correctness must be enforced where the data lives, not only by the node that believes it is in charge.
Clocks: why time is not what it seems
#Every machine has a clock, and the clocks disagree.
- Skew and drift: hardware clocks run slightly fast or slow. Synchronisation protocols (such as NTP) correct them, but only to within some error, which depends on the network.
- Jumps: when a clock is corrected, it can jump forward or backward. A timestamp taken later can be smaller than one taken earlier.
- Two kinds of clock: a wall clock (time of day, suitable for showing users and for logs) and a monotonic clock (only moves forward, suitable for measuring durations and timeouts on one machine). Use the monotonic one for "how long did this take?".
Wall clocks cannot order events across machines
#Suppose server B records "post created" and sends it to server A, which then records "comment added". If A's clock runs 50 ms behind B's, the comment can carry an earlier timestamp than the post it replies to. Sorting by timestamp shows the reply before the post. Worse, a database using last write wins by timestamp can silently keep the older of two writes.
Logical clocks
#In 1978, Leslie Lamport showed how to order events consistently with cause and effect without synchronised clocks (Time, Clocks, and the Ordering of Events in a Distributed System). Each process keeps a counter:
- Increment it before each local event.
- Attach it to every message sent.
- On receiving a message, set the counter to
max(local, received) + 1.
If event X could have caused event Y (X happened before Y on the same process, or X's message was received before Y), then X's counter is smaller than Y's. This Lamport clock gives an order that respects causality.
The program below simulates the post-and-comment example: wall clocks with 50 ms of skew put the comment first; Lamport clocks put the post first.
// File: ClockDemo.javaimport java.util.ArrayList;import java.util.Comparator;import java.util.List;public class ClockDemo { record Event(String what, String server, long wallClockMs, long lamport) {} public static void main(String[] args) { long skewOfA = -50; // server A's wall clock runs 50 ms behind true time long lamportA = 0; long lamportB = 0; List<Event> events = new ArrayList<>(); // True time 1000 ms: server B creates a post. lamportB++; events.add(new Event("post created", "B", 1000, lamportB)); long stampOnMessage = lamportB; // B sends the post to A, with its Lamport time // True time 1003 ms: A receives it. Rule: max(local, received) + 1. lamportA = Math.max(lamportA, stampOnMessage) + 1; // True time 1004 ms: A records a comment on that post. lamportA++; events.add(new Event("comment added", "A", 1004 + skewOfA, lamportA)); List<Event> byWallClock = new ArrayList<>(events); byWallClock.sort(Comparator.comparingLong(Event::wallClockMs)); System.out.println("Sorted by wall clock:"); for (Event e : byWallClock) { System.out.println(" " + e.wallClockMs() + " ms " + e.what() + " (server " + e.server() + ")"); } List<Event> byLamport = new ArrayList<>(events); byLamport.sort(Comparator.comparingLong(Event::lamport)); System.out.println("Sorted by Lamport clock:"); for (Event e : byLamport) { System.out.println(" L=" + e.lamport() + " " + e.what() + " (server " + e.server() + ")"); } }}Sorted by wall clock: 954 ms comment added (server A) 1000 ms post created (server B)Sorted by Lamport clock: L=1 post created (server B) L=3 comment added (server A)Lamport clocks have a limit: if two events are concurrent (neither could have caused the other), their counters still compare, so the order between them is arbitrary. Vector clocks (a counter per process) can detect that two updates are concurrent, which is useful for spotting conflicts. Amazon's Dynamo paper describes using vector clocks to detect conflicting versions of the same object (Dynamo).
When you need real time
#Some systems do need globally meaningful timestamps. Google Spanner, for example, uses its TrueTime infrastructure, which exposes clock uncertainty explicitly and waits it out to give transactions a consistent global order (Spanner: TrueTime and external consistency). The lesson for ordinary designs is not to copy that, but to notice what it took: special hardware and careful protocol design. Without such infrastructure, do not rely on timestamps from different machines to decide which write wins.
Practical rules for time
#Need | Use |
|---|---|
Measure how long something took on one machine | Monotonic clock |
Show a time to a user, write a log line | Wall clock (synchronised, in UTC) |
Order events of one entity | A sequence number assigned in one place (a database row version, a per-conversation counter, a partition offset) |
Order events across machines consistently with causality | Logical clocks, or pass versions along with messages |
Decide which of two concurrent writes wins | An explicit rule (version check, merge, or reject), not "latest timestamp" |
Put together: what goes wrong in practice
#Situation | What can happen | Defence |
|---|---|---|
Client retries a timed-out payment | Customer charged twice | Idempotency key (article 20) |
Worker crashes after sending an email, before acknowledging | Email sent twice | Idempotent consumer, deduplication |
Two updates to one record arrive out of order | Older value overwrites newer | Versions; conditional updates (article 14) |
Cache invalidation message lost | Stale data until TTL | TTLs on every entry (article 09) |
Network partition between primary and failover manager | Two primaries (split brain) | Fencing; majority-based election (article 22) |
Leader pauses for 20 seconds, resumes | Two nodes act as leader | Leases plus fencing tokens (article 22) |
Timestamps from skewed clocks decide conflicts | Newer write silently lost | Versions or explicit conflict handling |
One slow dependency in a synchronous chain | Threads exhausted, cascading failure | Timeouts, circuit breakers, bulkheads (article 23) |
Design principles that follow
#These habits appear in every remaining article. Learn them as a list:
- Assume every remote call can fail, hang, or succeed without telling you. Give it a timeout and a plan.
- Make operations idempotent so retries and redeliveries are safe.
- Prefer at-least-once delivery plus deduplication over hoping for exactly-once.
- Do not order events by wall-clock time across machines. Use sequence numbers and versions.
- Enforce correctness where the data lives (constraints, conditional writes, fencing), not only in the node that thinks it is in charge.
- Keep the synchronous critical path short. Every hop is another way to fail.
- Design for partial failure: degrade a feature instead of failing everything.
- Make the system observable, because partial failures are invisible without good signals (article 25).
- Prefer simple designs. Each extra component and protocol multiplies the failure combinations.
Trade-offs
#Decision | You gain | You pay |
|---|---|---|
Short timeouts | Fast failure, protected resources | More false timeouts and retries |
Long timeouts | Fewer false timeouts | Resources held by dead dependencies |
Retrying | Recovery from transient loss | Duplicates; extra load during incidents |
Logical clocks or versions | Correct ordering and conflict detection | More metadata and logic |
Synchronous coordination | Simple reasoning | Availability and latency coupled across machines |
Common mistakes
#- Treating a timeout as "it failed" and blindly retrying a non-idempotent operation.
- No timeouts, or one fixed timeout for everything.
- Using
System.currentTimeMillis()(or any wall clock) differences across machines for ordering or conflict resolution. - Assuming messages arrive once and in order.
- Assuming a lock or a leadership role is still held because no error was seen.
- Testing only the happy path; never injecting delays, drops or duplicates.
In an interview
#- When a design calls another service, say what happens on timeout, and whether retries are safe.
- When ordering matters, say where the sequence number comes from.
- When something is replicated or partitioned, mention partial failure: "if the link between zones drops, this side will..."
- Use the vocabulary precisely: partial failure, idempotent, at-least-once, clock skew, fencing.
Exercises
#Exercise 1
The three outcomes
#A client calls POST /v1/orders and the request times out. Then a client calls GET /v1/orders/77 and it times out. For each, list what might have happened on the server, and what the client should do next.
Exercise 2: Choose a timeout
#A recommendation service your API calls has these latencies: p50 40 ms, p99 300 ms, p99.9 900 ms, p99.99 3 seconds. Your API must respond within 1 second at p99, and it also calls a 200 ms profile service in sequence. What timeout would you set on the recommendation call, and what does your API do when it fires?
Exercise 3: Last write wins
#Two servers handle edits to the same document. Server X's clock is 2 seconds ahead. A user saves version 1 through X at true time 10:00:00 (stamped 10:00:02), then saves version 2 through Y at true time 10:00:01 (stamped 10:00:01). The database keeps the write with the larger timestamp. What does the user see, and how would you fix the design?
Exercise 4: Name the assumption
#For each bug, name the false assumption about distributed systems behind it:
- A job scheduler on two servers sometimes runs a job twice after a network blip.
- A notification worker sends duplicates after deployments.
- Analytics show some users "commenting before the post existed".
- During a dependency outage, the API's thread pool fills and the whole service stops responding.
Show solutionHide solution
POST /v1/orders timed out. The order may not exist (request lost), may exist (created, response lost), or may be about to exist (still processing). The client should retry with the same idempotency key, so the server returns the original order if it was created, or creates it once if not. If the client has no idempotency key, it should look the order up (for example by a client-generated reference) before trying again, rather than blindly re-posting, which risks a duplicate order.
GET /v1/orders/77 timed out. Reading changes nothing, so any of the possibilities is harmless. The client can simply retry (with backoff), because GET is idempotent.
Exercise 2
Show solutionHide solution
The total budget is 1 second at p99. The profile call takes about 200 ms, leaving roughly 800 ms, minus your own processing and network overhead, say 150 ms: about 600 ms is the most the recommendation call can have. Its p99.9 is 900 ms, so a 600 ms timeout will cut off somewhat more than 0.1% of calls (somewhere between p99 and p99.9), which is acceptable if you have a fallback.
When it fires: degrade gracefully instead of failing the request. Return the page with generic or cached recommendations (or none), log and count the timeout, and do not retry inside the request (there is no budget). If timeouts rise sharply, a circuit breaker can stop calling the service for a while (article 23).
Better still, make the two calls in parallel if they are independent: the budget for each becomes most of the second.
Exercise 3
Show solutionHide solution
Version 1 carries the timestamp 10:00:02 and version 2 carries 10:00:01. The database keeps the "later" timestamp, which is version 1. The user's most recent save (version 2) is silently discarded; they reload and see their older text. No error was raised anywhere.
Fixes:
- Version numbers instead of timestamps: each save sends the version it was based on; the database accepts it only if the version matches, then increments it (optimistic concurrency, article 14). Version 2 is based on version 1 and wins; a truly concurrent edit gets a conflict to resolve.
- A single authority for ordering: route all writes for one document to one place (one database primary, or one partition) and let it assign a sequence number.
- Keep clocks synchronised anyway (for logs), but never let them decide which data wins.
Exercise 4
Show solutionHide solution
- "A node always knows whether it is the leader / holds the lock." After the blip, both servers believed they were in charge. Fix with proper leader election and fencing, plus idempotent jobs (articles 22 and 37).
- "Each message is delivered exactly once." Deployments kill workers mid-processing; messages are redelivered. Fix with idempotent consumers and graceful shutdown (articles 11 and 20).
- "Clocks on different machines agree." Events were ordered by skewed wall clocks. Fix with logical ordering or causal metadata.
- "The network is reliable and fast; latency is zero." Calls without timeouts or isolation tied up every thread. Fix with timeouts, bulkheads and circuit breakers (article 23).
Where to go next
#- Next: CAP Theorem and Consistency Models Explained Without the Myths asks what "consistent" can mean when replicas and partitions are involved.
- Then: idempotency and retries (article 20), distributed transactions (article 21), and locks and leader election (article 22) answer the problems listed here.
Sources & further reading
- Technology
- Software Engineering
- #consistency
- #distributed systems
- #reliability
- #roadmap
- #scalability
- #system design
- #system design interview
Connected lessons
Linked from 4 guides
Other pages in the library that point here.
Keep reading
More in Technology →Guide · Technology
Start Here: Your Roadmap from Zero to Job-Ready DSA
Guide · Technology
Heaps and Priority Queues
Guide · Career
A LinkedIn Profile That Gets Recruiter Messages

