System Design (High-Level Design) Module 5 – APIs and communication patterns
Synchronous vs asynchronous communication
When a caller should wait for the answer and when it should hand the work to a queue, a status URL or a callback, and what each choice costs.
What you will learn
- Calculate the availability of a chain of synchronous calls
- Apply the asynchronous request-reply pattern with 202 Accepted
- Compare polling and callbacks by the requests a second they cost
- Protect a queue consumer against redelivery, poison messages and reordering
- Decide for each operation whether the caller should wait for the result
Before you start
What you’ll learn · Before you start
What you will learn
- Calculate the availability of a chain of synchronous calls
- Apply the asynchronous request-reply pattern with 202 Accepted
- Compare polling and callbacks by the requests a second they cost
- Protect a queue consumer against redelivery, poison messages and reordering
- Decide for each operation whether the caller should wait for the result
Before you start
On this page
Checkout waits on five services in turn, each up 99.9 % of the time, so it fails whenever any one of them is down: about 216 minutes of failed orders a month. A call is synchronous when the caller waits for the answer before it does anything else, and asynchronous when it hands the work over, to a queue or to a job that reports back, and carries on.
Wait when the caller cannot continue without the answer and the callee is fast and dependable, as with reading a price or authorising a payment. Hand the work over when it is slow, can safely happen later, or must not take the caller down when it fails, as with emails, reports and search indexing. A sound default for user-facing writes: keep the user waiting only until the change is durably committed, and do everything after the commit asynchronously.
Waiting couples the caller to every callee
A synchronous call creates temporal coupling: The dependence a synchronous call creates between a caller and its callee: both must be up, and fast, at the same moment. Along a chain of such calls, availabilities multiply and latencies add.: the caller and the callee must both be up, and fast, at the same moment. The coupling compounds along a chain, because every service the request waits on is a hard dependency: A part that every request needs, so the service fails whenever it does. Hard dependencies are in series, and each one lowers the availability the whole service can reach. More in “Availability maths: nines, series and parallel”. If checkout waits on five services in turn and each is up 99.9 % of the time, an order succeeds only when all five are, so its availability is the product of theirs when they fail independently:
"""How available is a request that waits on a chain of services? Each service is up 99.9 % of the time, and
failures are assumed independent (shared causes such as one database make the real numbers worse)."""
MINUTES_A_MONTH = 30 * 24 * 60
EACH = 0.999
print("services the request waits on available down a month")
for n in range(1, 7):
up = EACH**n
print(f"{n:>29} {up:>9.2%} {(1 - up) * MINUTES_A_MONTH:>6.0f} min")
# Asynchronous after the commit: the request needs only the API and the queue; the workers behind the queue may
# be down without failing it (its work waits instead).
up = EACH**2
print(f"\nAPI and queue only (workers behind the queue) {up:.2%} {(1 - up) * MINUTES_A_MONTH:.0f} min") Output
services the request waits on available down a month
1 99.90% 43 min
2 99.80% 86 min
3 99.70% 129 min
4 99.60% 173 min
5 99.50% 216 min
6 99.40% 259 min
API and queue only (workers behind the queue) 99.80% 86 min
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 chain_availability.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
Five services at 99.9 % leave checkout at 99.5 %: about 216 minutes of failed orders a month instead of 43. Their latencies add up along the chain too, so every callee’s slow moments become the shopper’s wait. Asynchronous communication breaks the chain at the commit: the request needs only the API and a durable queue, and the work behind the queue waits, instead of failing, while its service is down. Follow one order through both designs during a 30-second email outage:
The drawing shows the whole scene; its 8 steps are listed below.
Every step as text
- Design A, an order arrives. A shopper places an order. In design A, the checkout API calls all five services in turn and answers only when every one of them has answered.
- Two calls succeed. Inventory reserves the stock and payments authorises the card. The order still waits, because three more calls are in its path.
- Email is down. The email service is in the middle of its 30-second outage, so the third call times out, and the whole order fails with it although the stock and the money were fine.
- Five dependencies, one product. Each service is up 99.9 % of the time, and an order needs all five at once, so checkout can take orders 0.999 to the fifth power of the time, 99.5 %, about 216 minutes of failed orders a month.
- Design B, two calls, then commit. Now the same order in design B. Checkout calls inventory and payments, whose answers the order needs, commits the order and puts one message on a queue.
- The order succeeds. The shopper gets a confirmed order at once. Email, loyalty and analytics are no longer in the order's path, so their outages cannot fail it.
- The work waits, not fails. The email worker cannot send the receipt while its service is down, so the message stays in the queue. The outage costs a delay, not an order.
- Email is back. When the email service returns, the queue delivers the message and the receipt goes out about 30 seconds late. Loyalty and analytics take theirs at their own pace.
With a queue in front of the email service, a 30-second email outage becomes 30 seconds of late emails instead of 30 seconds of failed orders. Both designs side by side, and three ways to learn the outcome:
The same checkout, synchronous throughout or asynchronous after the commit
Text description of the diagram
The figure has two panels, one under the other.
- Synchronous: the order waits on all five. A shopper places an order with the checkout API, which calls five services one after another: inventory, payments, email, loyalty and analytics. If any of the five fails, the order fails.
- Asynchronous after the commit. The shopper places an order with the checkout API, which calls only inventory and payments (steps 1 and 2, needed before the order can be committed) and commits the order to the orders database (step 3). An "order placed" message then goes from the committed order to a queue (step 4). The email, loyalty and analytics workers take messages from the queue later, at their own pace, so their outages delay that work instead of failing the order.
| Criterion | Synchronous | Polled | Called back |
|---|---|---|---|
| Caller waits for | The result | A 202 and a status URL | An acknowledgement only |
| Worker is down | The request fails | The work waits | The work waits |
| Outcome arrives | In the response | When a poll finds it finished | When the callback or event arrives |
| Hard parts | Timeouts, retries, tail latency | Poll traffic, status storage, expiry | A reachable endpoint, signatures, duplicates |
| When to choose | The caller cannot go on without the answer, and the callee is fast | Slow jobs for clients that cannot receive calls, such as browsers | Slow jobs for servers that can receive calls, and events after a commit |
The ways to communicate, by name:
- Synchronous request-response: the caller sends a request and blocks until a reply or a timeout.
- asynchronous request-reply: An API pattern for work that outlives a request: the server answers 202 Accepted at once, with the URL of a status resource that the client polls until the work is done.: the server answers 202 Accepted at once, with a status URL the client polls.
- Callbacks: the server calls the client back, for example through a webhook, when the work is done.
- Queues: a producer leaves a message, and one of several consumers processes it when it is ready.
- publish-subscribe: A messaging style in which a producer publishes an event and every subscriber receives its own copy and acts on it in its own time, without the producer knowing who listens.: a producer publishes an event, and every subscriber receives it and acts in its own time.
Asynchronous communication has its own costs: it trades an outage for a delay, and adds duplicate deliveries, messages out of order and failures that nobody is waiting to see.
Key idea
A caller that waits needs every callee up at that moment, so availabilities multiply along the chain; work that can follow the commit belongs behind a queue, where an outage becomes a delay.
Checkpoint
Check: How waiting couples a caller to its callees
Measuring the coupling over one simulated day Pro: opens with a Pro, Premium or Ultimate pass
In this part:
- The asynchronous request-reply pattern, step by step
- Polling or callbacks: the cost, in requests a second
- Failure drill: duplicates, a poison message and reordering
- Four questions choose per operation
- Practise: a consumer that survives redelivery
- A consumer that survives redelivery and reordering
- Interview questions
- Checkout calls inventory, payments, fraud scoring, email and loyalty services synchronously, one after another. Last week the email provider was down for 40 minutes and no orders could be placed. Redesign which calls stay synchronous, and say what the shopper sees now.
- Design the API that exports a merchant's transactions as a file. Small exports finish in 2 seconds, large ones take 20 minutes. Walk through every request and response from the first click in the browser to the download, including the failures.
- After loyalty points and address changes moved to a queue with several consumers, some customers got double points and some addresses went back to an old value. Explain both bugs from the queue's delivery guarantees, then fix them without giving up parallel consumers.
- Key takeaways
3 diagrams · 3 runnable examples (Python) · 1 exercise with sample tests · 3 interview answers · about 51 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
Terms in this lesson
- asynchronous request-reply
- An API pattern for work that outlives a request: the server answers 202 Accepted at once, with the URL of a status resource that the client polls until the work is done.
- publish-subscribe
- A messaging style in which a producer publishes an event and every subscriber receives its own copy and acts on it in its own time, without the producer knowing who listens.
- temporal coupling
- The dependence a synchronous call creates between a caller and its callee: both must be up, and fast, at the same moment. Along a chain of such calls, availabilities multiply and latencies add.
References
- Asynchronous Request-Reply pattern (Azure Architecture Center) (Microsoft)
- Queue-Based Load Leveling pattern (Azure Architecture Center) (Microsoft)
- Competing Consumers pattern (Azure Architecture Center) (Microsoft)
- RFC 9110: HTTP Semantics (202 Accepted, 303 See Other, Retry-After) (IETF)
- Site Reliability Engineering, chapter 21: Handling Overload (Google (O'Reilly Media))
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress