0% completed
Request-Response
On This Page
The Pattern
The Decision: One Question Per Arrow
When NOT to Use It
The Interview Lens
Check Yourself
Composes With
TL;DR Card
The Pattern
The caller sends a request and waits, right there, until the answer comes back: a result or an error, before anything else happens.
This is the phone call from the module intro. You dial, you ask, you stay on the line, you get your answer. It's how almost all communication between services works, and it's the default: the other six patterns in this module are deliberate ways of saying "actually, let's not make a phone call here."
Why does the default need its own lesson? Because of a funny thing that happens to engineers who just learned about queues and events: they start bolting them onto everything. But most traffic in most systems is, correctly, a simple 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, it's request-response. The user is the person on hold, and no queue changes that.
Here's what the phone call buys you:
- The answer, right now. Your next line of code can use the result. There's no "we got your request, check back later" state to design around.
- Errors that land somewhere useful. If the call fails, the failure arrives at code that can do something about it: retry, show a message, fall back. Failed background work is much harder to trace back.
- Easy debugging. One request, one path through the system, one trace to read.
And here's what it costs. Each cost is simple on its own; they add up in chains.
- Both sides must be up at the same moment. If the service you're calling is down right now, your call fails right now. Engineers call this temporal coupling: you're coupled in time.
- Fragility multiplies down a chain. Say your endpoint calls 5 services, one after another, and each one is up 99.9% of the time. Your endpoint only succeeds when all five succeed. So multiply: 0.999 × 0.999 × 0.999 × 0.999 × 0.999 ≈ 0.995. That's 99.5%, which sounds fine but means roughly 44 hours of failures per year. Every extra hop makes it worse.
- Latency adds up. Five calls at 80 ms each, one after another, is 400 ms. But notice: if those five calls don't depend on each other, you can fire them all at once, in parallel. Then you only wait for the slowest one, roughly 80 ms. Turning accidental one-after-another calls into parallel ones is the most common free speed win in slow endpoints.
- Waiting isn't free. A caller that's waiting is holding a thread or a connection the whole time. Do the math at scale: 300 requests per second, each waiting 2 seconds, means 600 requests are "on hold" at any moment, each occupying a thread. That's why request-response at scale always travels with three protections: a timeout (give up after a deadline), retries (try again on hiccups), and a circuit breaker (stop calling something that's clearly down). Each has its own lesson later; for now, think of them as the seatbelt, airbag, and brakes of every synchronous call.
One practical detail that punches above its weight: opening a brand-new connection is expensive. The TCP and TLS handshakes can take one to three network round trips, around 100 ms across regions. Keeping connections open and reusing them (called connection pooling or keep-alive) makes calls cheap. Every serious HTTP client does this; make sure yours actually has it turned on.
The Decision: One Question Per Arrow
Before you draw any arrow, ask: does the caller's next step depend on the answer, and is anyone waiting? If yes to both, it's a phone call. If not, one of the other patterns fits better:
| The situation | Sounds like | Use |
|---|---|---|
| I need the answer to continue; someone waits | A phone call | Request-response (this lesson) |
| Do this task; later is fine | A to-do list | Message Queue |
| Something happened; whoever cares should know | A group announcement | Pub/Sub |
| Another company needs to hear about it | Calling their office | Webhooks |
| A stream of updates flowing one way | A news ticker | Server-Sent Events |
| Both sides talking, live | A walkie-talkie | Bidirectional Streaming |
The upgrades are never free. Each one adds a pending state, possible duplicates, or a connection to manage. Escalate because the answer isn't needed now, not because a pattern sounds impressive.
When NOT to Use It
- Long-running work. Anything that takes more than a few seconds shouldn't keep a caller on hold. Accept the job, return a "job started" response right away, and deliver the result through a queue, a status the client can check, or a webhook.
- Telling many parties about one event. Calling five services one by one, just because they care about an order, is the mess the Pub/Sub lesson opens with. Announce it instead.
- Bursty work with relaxed deadlines. The Message Queue lesson opens with a flash sale that crushed a synchronous pipeline. When nobody's waiting, don't force the work to happen in real time.
- Chains that have grown too deep. Six synchronous hops means the availability and latency math above has already turned against you. Shorten the chain, parallelize, cache, or move some hops to async.
And the opposite mistake, the quiet epidemic of this module: async by reflex. Putting a search query on Kafka. Making the login check "event-driven." The user is still sitting there waiting, and now there's a 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 a virtue.
The Interview Lens
How it's asked: almost never directly. The interviewer watches which arrows in your diagram are synchronous, then points at one: "Why is this a synchronous call?" The strong answer names the dependency out loud: "Checkout can't 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 land 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 hop in timeout-retry-breaker protection, and move anything nobody is waiting on to a queue or an event."
Likely follow-ups:
- "Your endpoint calls five services and it's slow. Fix it without going async." Run the independent calls in parallel (the total drops from the sum to the slowest one). Cache the answers that rarely change. Ask whether every hop truly belongs in this path. And check that connections are actually being reused.
- "When do you switch a call to async?" When nobody needs the answer to proceed, or the work outlives a reasonable connection. And say the honest part: the switch buys resilience but adds pending states, duplicates, and ordering questions. It's a trade, not an upgrade.
Red flag that fails candidates: making a user-facing path asynchronous ("the search goes into Kafka"), or drawing six synchronous hops with no failure handling and no sense of the availability math. Same gap, two directions: not knowing what synchrony costs, or not knowing what it's worth.
Check Yourself
Q1 (recall). What two questions decide sync versus async? And what three costs does every synchronous hop add to a chain?
Q2 (trade-off). A checkout page makes five sequential 80 ms calls to services that are each 99.9% available. Work out its latency and availability. Then improve both without any async machinery.
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 per-hop costs: both sides must be up at the same moment (temporal coupling), availability multiplies (0.999 per hop, compounding), and latency adds when calls run one after another. Plus the hidden fourth: every waiting caller holds a thread, which is why the timeout-retry-breaker kit is mandatory at scale.
A2. Latency: 5 × 80 ms = 400 ms. Availability: 0.999 multiplied five times ≈ 99.5%, which is about 44 hours of failed checkouts a 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 like pricing rules, so some hops usually disappear; and drop any call whose result isn't truly needed to render the page. For availability, decide which calls are allowed to fail without failing the whole page; the graceful degradation lesson turns that idea into a full method.
A3. Push back, using this module's own logic. Checkout's next step depends on the authorization result, and the user is waiting. Making it async doesn't remove the need for the answer. It just moves the user into a pending-state limbo that product now has to design ("what does the screen show while payment.requested floats around?"), and it adds duplicate-authorization risk. 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 can't double-charge, a circuit breaker that fails closed, and a deliberately designed degraded mode (saved cart, "we'll email you when payment completes") for real outages. Async is the right call for the receipt email, which is exactly why that's already on a queue.
Composes With
- Timeout / Retry / Circuit Breaker: the survival kit; request-response at scale is unsafe without all three.
- Message Queue: the first escalation, for work nobody's waiting on.
- Load Balancing: how one logical callee becomes many physical ones under this pattern.
- API Gateway: where external request-response traffic is policed and shaped.
TL;DR Card
| <div style="width:75px">Problem </div> | Every arrow between services needs a default contract, and both mistakes are common: going async on paths where someone is waiting, and stacking unexamined synchronous hops until latency and fragility compound. |
| Mechanism | The caller waits for the answer: result or error, now. Pool and reuse connections, run independent calls in parallel, and wrap every hop with timeout, retry, and circuit breaker. |
| Costs | Both sides must be up at the same moment, availability multiplies per hop (0.999ⁿ), latency adds when sequential, and every waiting caller holds a thread. |
| Skip it when | Nobody's waiting, the work takes too long for a held connection, many parties care about the same event, or the chain has grown past what the math forgives. |
| 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 awaits moved to queues and events instead. |
On This Page
The Pattern
The Decision: One Question Per Arrow
When NOT to Use It
The Interview Lens
Check Yourself
Composes With
TL;DR Card