0% completed
Request-Response
On This Page
- The Pattern
- What It Gives You
- What It Costs
- One Question Per Arrow
- When Not to Use It
- In the Interview
- Check Yourself
- Related Patterns
- TL;DR
1. The Pattern
The caller sends a request and waits until the answer comes back, either a result or an error, before doing anything else.
This is how almost all communication between services works, and it is the default. The other six patterns in this module are deliberate ways of deciding not to make a direct call.
Why does the default need its own lesson? Because engineers who have just learned about queues and events tend to add them everywhere. In reality, most traffic in most systems is, correctly, a simple synchronous call: loading a page, running a search, checking a password, looking up a price, authorizing a payment. If a user is actively waiting for the result, use request-response. A queue does not change the fact that someone is waiting.
2. What It Gives You
- The answer, immediately. Your next line of code can use the result. There is no "we received your request, check back later" state to design around.
- Errors arrive where you can handle them. If the call fails, the failure is returned to code that can retry, show a message, or use a fallback. A failed background job is much harder to trace.
- Simple debugging. One request, one path through the system, one trace to read.
One practical detail matters more than it seems: opening a new connection is expensive. The TCP and TLS handshakes can take one to three network round trips, which is around 100 ms across regions. Keeping connections open and reusing them (connection pooling, or keep-alive) makes calls cheap. Every serious HTTP client supports this; confirm that yours has it enabled.
3. What It Costs
Each cost is small on its own. They add up in chains of calls.
- Both sides must be up at the same moment. If the service you are calling is down right now, your call fails right now. This is called temporal coupling.
- Availability multiplies down a chain. Suppose your endpoint calls 5 services in sequence, and each is available 99.9% of the time. Your endpoint succeeds only when all five succeed: 0.999 x 0.999 x 0.999 x 0.999 x 0.999 = about 99.5%. That sounds fine, but it means roughly 44 hours of failures per year. Every additional call makes it worse.
- Latency adds up. Five sequential calls at 80 ms each take 400 ms. However, if the five calls do not depend on each other, run them in parallel and you only wait for the slowest one, roughly 80 ms. Converting accidental sequential calls into parallel calls is the most common quick improvement in slow endpoints.
- Waiting holds resources. A waiting caller holds a thread or connection the whole time. At 300 requests per second with a 2-second wait, 600 requests are in progress at any moment, each holding a thread. This is why request-response at scale always needs three protections: a timeout to give up after a deadline, retries for brief failures, and a circuit breaker to stop calling a service that is clearly down. Each has its own lesson later in the course.
4. One Question Per Arrow
Before drawing any arrow in a design, ask: does the caller's next step depend on the answer, and is anyone waiting? If yes to both, make a direct call. If not, one of the other patterns fits better:
| The situation | Use |
|---|---|
| The next step needs the answer, and someone waits | Request-response (this lesson) |
| The work can happen later | Message Queue |
| Several services need to know something happened | Publish-Subscribe |
| Another company's system needs to be notified | Webhooks |
| Updates flow continuously in one direction | Server-Sent Events |
| Both sides send messages continuously | Bidirectional Streaming |
The upgrades always have a cost. Each one adds a pending state, possible duplicates, or a connection to manage. Switch patterns because the answer is not needed immediately, not because a pattern sounds impressive.
5. When Not to Use It
- Long-running work. Anything that takes more than a few seconds should not keep a caller waiting. Accept the job, return a "job started" response immediately, and deliver the result through a queue, a status endpoint, or a webhook.
- Notifying many services about one event. Calling five services one by one because they all care about an order is the problem the Publish-Subscribe lesson opens with. Publish the event instead.
- Bursty work with relaxed deadlines. The Message Queue lesson opens with a flash sale that overwhelmed a synchronous pipeline. When nobody is waiting, the work does not need to happen immediately.
- Chains that have grown too deep. Six synchronous calls in sequence means the availability and latency math above has already become a problem. Shorten the chain, parallelize, cache, or move some calls to a queue.
The opposite mistake is just as common: going asynchronous by habit. Putting a search query on Kafka, or making the login check event-driven. The user is still waiting, and now there is a message broker in the middle adding delay and duplicate risk to a path that needed an answer in 200 ms. Asynchrony is a tool for when waiting is optional. It is not automatically better.
6. In the Interview
Interviewers almost never ask about this pattern directly. They watch which arrows in your diagram are synchronous, then point at one: "Why is this a synchronous call?" A strong answer names the dependency: "Checkout cannot continue without the payment result, and the user is waiting. So this stays synchronous, with a timeout, a retry budget, and a circuit breaker around it."
The 30-second answer: "Request-response is my default for anything the caller's next step depends on: auth, pricing, payment authorization, every interactive read. I get the answer immediately, errors are returned where I can handle them, and tracing is simple. The costs are that both sides must be up at once, availability multiplies down a chain, and latency adds up. So I run independent calls in parallel, reuse pooled connections, wrap every call with timeout, retry, and circuit breaker, and move anything nobody is waiting on to a queue or an event."
Likely follow-ups:
- "Your endpoint calls five services and it is slow. Fix it without going async." Run the independent calls in parallel, so the total drops from the sum to the slowest call. Cache the answers that rarely change. Ask whether every call truly belongs in this path. Confirm connections are being reused.
- "When do you switch a call to async?" When nobody needs the answer to proceed, or when the work takes longer than a connection should stay open. Then state the honest part: the switch adds resilience, and it also adds pending states, duplicates, and ordering questions. It is a trade, not an upgrade.
A mistake that fails candidates: making a user-facing path asynchronous ("the search goes into Kafka"), or drawing six synchronous calls with no failure handling. Both show the same gap: not knowing what synchrony costs, or not knowing what it is worth.
7. Check Yourself
Q1 (recall). Which two questions decide sync versus async? And what three costs does every synchronous call add to a chain?
Q2 (trade-off). A checkout page makes five sequential 80 ms calls to services that are each 99.9% available. Calculate its latency and availability. Then improve both without making anything asynchronous.
Q3 (scenario). A teammate proposes making payment authorization asynchronous "for resilience": checkout publishes a payment.requested event, and a worker authorizes it later. Assess the idea.
A1. The questions: does the caller's next step depend on the answer, and is anyone (human or machine) actively waiting? The costs per call: both sides must be up at the same moment (temporal coupling), availability multiplies (0.999 per call, compounding), and latency adds when calls run in sequence. There is also a fourth: every waiting caller holds a thread, which is why timeout, retry, and circuit breaker are mandatory at scale.
A2. Latency: 5 x 80 ms = 400 ms. Availability: 0.999 multiplied five times = about 99.5%, which is about 44 hours of failed checkouts per year. Improvements that stay synchronous: run the independent calls in parallel (latency falls to roughly the slowest call, 80-120 ms); cache the stable answers, such as pricing rules, so some calls usually disappear; and remove any call whose result is not needed to render the page. For availability, decide which calls may fail without failing the whole page. The graceful degradation lesson turns that idea into a full method.
A3. Argue against it, using this module's own logic. Checkout's next step depends on the authorization result, and the user is waiting. Making it async does not remove the need for the answer. It moves the user into a pending state that the product team now has to design around ("what does the screen show while payment.requested is being processed?"), and it adds the risk of duplicate authorizations. Real resilience for this path comes from the protective wrappers, each with its own lesson: a timeout, retries with an idempotency key so a retry cannot double-charge, a circuit breaker, and a designed fallback for real outages (save the cart, email the customer when payment completes). Async is the right choice for the receipt email, which is exactly why that is already on a queue.
8. Related Patterns
- Timeout, Retry, Circuit Breaker: the three protections. Request-response at scale is unsafe without all three.
- Message Queue: the first alternative, for work nobody is waiting on.
- Load Balancing: how one logical callee becomes many physical servers under this pattern.
- API Gateway: where external request-response traffic is authenticated and rate-limited.
9. TL;DR
| Problem | Every arrow between services needs a default, and both mistakes are common: going async where someone is waiting, and adding synchronous calls in sequence until latency and fragility compound. |
| Mechanism | The caller waits for the answer: a result or an error, now. Pool and reuse connections, run independent calls in parallel, and wrap every call with timeout, retry, and circuit breaker. |
| Costs | Both sides must be up at the same moment, availability multiplies per call, latency adds when sequential, and every waiting caller holds a thread. |
| Skip it when | Nobody is waiting, the work takes too long for a held connection, many services care about the same event, or the chain has grown too deep. |
| 30-second answer | The right default whenever the next step depends on the answer: a synchronous, traced, protected call, with independent calls in parallel, and everything nobody waits on moved to queues and events. |
Flashcards Review
What is the request-response pattern?
On This Page
- The Pattern
- What It Gives You
- What It Costs
- One Question Per Arrow
- When Not to Use It
- In the Interview
- Check Yourself
- Related Patterns
- TL;DR