skip to content

Blocking, Async & Streaming

Whether a handler holds a thread, awaits on a loop or streams its body, and what the framework does when a call blocks or a client vanishes. Probed because the wrong model is a classic outage.

on this pageshow

explore

questions

24

In a web framework, what does it mean for a request handler to return a future, promise or coroutine instead of a finished response?

level: juniorimportance: must knowfreq 70%

answer

  1. a claim ticket, not the goods
  2. handler returns before the body exists
  3. framework subscribes to the handle
  4. written on completion, not on return

basics

~20 s

The handler returns a handle to a response that does not exist yet. The framework leaves the exchange open, attaches a completion callback, and writes status, headers and body only once that handle completes with a value.

solid answer

~40 s

A value-returning handler computes the response and the framework serializes it the moment the call returns. An awaitable-returning handler instead starts the work and hands back a `future`, `promise` or suspended coroutine, then returns almost immediately. The framework recognises that return as a handle rather than a body, keeps the exchange open with nothing yet written to the socket, and subscribes to its completion. When the handle completes with a value, that value goes through the same return-mapping the framework applies to a synchronous return — negotiation, serialization, default status. When it completes with a failure, the framework routes it into its error stage. Returning a handle changes *when* the response is produced, not what the client sees.

go deeper

for a junior

Recall the one-line shape: the handler gives back a handle to a response that is not ready, and the framework fills in the response when that handle completes.

for a middle

Explain the mechanics: the framework recognises the awaitable return, attaches a completion callback, keeps the exchange uncommitted, then applies the ordinary return-mapping to the completed value or the error mapping to a failure.

for a senior

Show you can read the symptoms in production — an immediate empty success followed by late log lines, or failures appearing as unhandled errors rather than error responses, both point at a handle that was created but never returned.

for a principal

Frame it as a capacity decision rather than a style one: the handle buys in-flight concurrency, not per-request latency, so the payoff depends on how much of your request time is spent waiting rather than computing.

## Two shapes a handler can have A handler is the function a framework calls once routing has picked it. On most server-side frameworks it can be written in one of two shapes. - **Value-returning.** The handler computes everything it needs and returns the response itself — a body model, a status, or a response object. The framework converts that return into bytes as soon as the call returns. - **Awaitable-returning.** The handler starts the work and returns a *handle* to a result that does not exist yet: a **future**, a **promise**, or a coroutine that will suspend at its first wait point. The handle is a subscription, not a body. | | value-returning handler | awaitable-returning handler | |---|---|---| | what the return value is | the response, or the model for it | a handle to a future response | | when the response is written | when the handler returns | when the handle completes | | while the work is outstanding | control is still inside the handler | the handler has already returned | | where a failure surfaces | thrown out of the call | a failed completion of the handle | | what ends the exchange | the return | the completion callback | ## What the framework does with the handle 1. Routing picks the handler and the framework calls it. 2. The handler starts its work and returns a handle, usually within microseconds. 3. The framework recognises the return as awaitable — from the declared return type, from the registration style used to install the handler, or because every handler on that surface is awaitable — and does **not** try to serialize the handle itself as a body. 4. It attaches a completion callback and leaves the exchange open. Nothing has gone to the socket yet, so status, headers and body are all still changeable. 5. On successful completion it takes the completed value and runs it through the same return-mapping a synchronous return would get: content negotiation, serialization, a default status when none was set. 6. On failed completion it hands the failure to the error stage rather than the serializer. ## Completion carries a value or a failure An awaitable settles exactly once, in one of two ways. A **success** carries the value the handler would otherwise have returned, and the framework converts it normally. A **failure** carries an error, and the framework's error mapping — the same stage that turns a thrown error into a status and a body — gets it instead. Both paths end with the framework, not the handler, writing the response; the handler's job finished the moment it returned the handle. A third outcome is possible and is the dangerous one: the handle **never settles**. There is then no completion callback to fire, so the framework has nothing to write and the exchange simply stays open, holding whatever per-request state it allocated, until some unrelated mechanism tears it down. ## What returning a handle does not change - It does **not** make a blocking call non-blocking. Blocking work inside the handler still occupies whatever thread is running it; wrapping it in a handle only changes the shape of the return. - It does **not** change what the client sees. The protocol carries a status line, headers and a body; it has no notion of how the server produced them. - It does **not**, by itself, speed up a single request. The latency of one request is still dominated by its slowest dependency; what changes is how many requests the process can hold in flight while waiting. - It does **not** exempt the result from validation, negotiation or serialization — the completed value meets the same rules as any return. ## Frameworks differ in how they recognise it Frameworks differ here, and it is worth knowing which style you are in: some inspect the handler's declared return type and adapt whatever awaitable shape they find; some require the handler to be installed through a separate registration so the framework knows in advance; and some make every handler awaitable so the question never arises. The observable behaviour — response written on completion, failure routed to the error stage — is broadly the same across all three. ## Symptoms that you got the shape wrong - The client gets an immediate empty success while log lines from the handler keep arriving afterwards: the handle was created but not returned, so the framework thought the handler was done. - Failures show up as unhandled-error log entries instead of error responses: the failure settled a handle nobody was subscribed to. - Throughput does not improve after a conversion: something on the path still blocks while the handle is outstanding.

  • How does the framework know a handler wants this treatment rather than serializing the handle as a body?
    By the registration style or the declared return type. Some frameworks inspect what the handler declares it returns and adapt any awaitable shape they recognise; others require the handler to be installed through a separate registration; others make every handler awaitable. If none of those signals is present, the framework is likely to treat the handle as an ordinary value and serialize it, which typically ships the wrapper's internals or an empty object.
  • If the handler has already returned, why can the framework still set the status code?
    Because nothing has been written to the socket yet. The response is committed at the first flush of bytes, not at the handler's return, so between the return and the completion the status and headers are still mutable. That is exactly the window the framework uses to map a failed completion onto an error status.
  • Does an awaitable-returning handler make an individual request faster?
    No. One request still waits for its own dependencies, and a handle adds a small amount of scheduling overhead. The benefit is concurrency: while a request waits, the resource that would have been pinned to it is free to carry other requests, so the process holds far more requests in flight at the same total latency.

It is a claim ticket at a counter. The handler hands back a ticket rather than the parcel, and the counter stays open until the ticket is redeemed — at which point the clerk, not you, packs and sends the parcel.

saying these in an interview costs you the question

  • Thinks returning a handle makes a blocking call non-blocking
  • Believes the response is written when the handler returns
  • Says the framework waits on the handle before continuing
  • Claims an awaitable handler makes each request faster
  • Forgets a failed handle still needs error mapping
  • Thinks the client can tell which handler shape was used
open as a page

In server-side web frameworks, which thread runs your handler under thread-per-request, event-loop, and coroutine execution models?

level: juniorimportance: must knowfreq 70%

basics

~20 s

Thread-per-request frameworks give each request a pooled worker thread for the whole exchange. Event-loop frameworks run handlers as short turns on a few shared loop threads. Coroutine frameworks suspend the handler at await points and release the carrier thread meanwhile.

open as a page

Why would a web framework handler stream a large response body incrementally instead of building the whole body in memory first?

level: juniorimportance: must knowfreq 60%

basics

~20 s

Streaming keeps memory flat and bytes moving. The handler writes pieces the framework sends as they are produced, so a large response costs a small buffer per request instead of its full size, and the client sees data sooner.

open as a page

In a server-side web framework, what does a configured request timeout actually do when a handler runs past it?

level: juniorimportance: must knowfreq 62%

basics

~20 s

A request timeout bounds how long the framework waits for a response, not how long the handler runs. When it fires, the framework abandons the result and writes an error response; the handler usually keeps executing.

open as a page

When a handler returns an awaitable that fails or never completes, how does a web framework turn that into a response?

level: middleimportance: must knowfreq 60%

basics

~20 s

A failed awaitable is delivered to the framework's error-mapping stage, the same one that handles a thrown error, and becomes an error status. An awaitable that never settles produces no response at all: the exchange stays open holding its resources.

open as a page

In a thread-per-request web framework, what caps the number of requests in flight, and what happens beyond that cap?

level: middleimportance: must knowfreq 62%

basics

~20 s

The handler worker count is the ceiling: one request occupies one worker for its whole exchange, waiting included. Beyond it requests sit in the server's connection or dispatch queue, so clients see queue time, then connection refusals or timeouts, while handler timings still look healthy.

open as a page

In a web framework, what becomes fixed the moment a streaming handler's first bytes are flushed to the client?

level: middleimportance: must knowfreq 58%

basics

~20 s

Headers travel ahead of the body, so the first flush freezes the status code, every header field, cookies and the body's framing choice. Later changes to them are lost; only more body bytes can follow.

open as a page

When a web framework times out a request, why can the handler keep holding a worker, and what decides whether it stops?

level: middleimportance: must knowfreq 66%

basics

~20 s

Nothing forcibly stops running code, so an abandoned handler keeps its worker until its current call returns. It stops early only if the framework signals cancellation and the handler reaches a point where it can observe it.

open as a page

In a web framework, what is an after-response hook for, and what are its limits as a place to run work?

level: middleimportance: must knowfreq 62%

basics

~20 s

An after-response hook runs framework-scheduled code once a request's response has been written, for cleanup, metrics and emitting events. It stays in the serving process and unsupervised: it cannot change the response, and its failures have no caller left to tell.

open as a page

A handler responds 200 and then runs the real work on a thread it spawned itself — what breaks in production?

level: seniorimportance: must knowfreq 66%

basics

~20 s

Nothing durable records that the work is owed, so a deploy, crash or scale-in silently loses whatever is in flight or queued in memory. There is also no backpressure, no retry, no dead-letter, and no caller left to report a failure to.

open as a page

Why does work deferred past the response often fail when it touches a request-scoped object or resource?

level: juniorimportance: should knowfreq 50%

basics

~20 s

Request-scoped objects — request and response, body streams, per-request context, a borrowed connection — are torn down or returned to a pool when the response ends. Deferred code holding a reference finds them closed, cleared, or already serving another request.

open as a page

Inside a handler that returns an awaitable, how do you hand a blocking call to a side pool without breaking the framework's contract?

level: middleimportance: should knowfreq 55%

basics

~20 s

Submit the blocking call to a bounded pool of your own, compose the response mapping onto the handle that submission returns, and return that handle. Never wait for the result inside the handler: that reintroduces the block.

open as a page

A streaming handler writes rows as it produces them, yet the client receives them all at once at the end — why?

level: middleimportance: should knowfreq 50%

basics

~10 s

Writing is not sending. The framework buffer, a content encoder, the socket, an intermediary and the client's parser can each hold bytes, so incremental writes arrive as one batch unless every layer is flushed.

open as a page

How does a server-side web framework notice that the client aborted the connection before the response was finished?

level: middleimportance: should knowfreq 54%

basics

~20 s

Two ways: a write to the closed connection fails, or the transport reports the close and the framework fires a disconnect event or cancellation signal. A handler that buffers notices nothing until its final write.

open as a page

In an app that mixes synchronous handlers with awaitable-returning ones, what breaks in shared wrappers and request-scoped state?

level: seniorimportance: should knowfreq 50%

basics

~20 s

A wrapper that does its after-work right after calling the next stage runs that work when the handler returns its handle, not when the response exists. Timings, cleanup and error catching all land in the wrong place.

open as a page

Why does per-request context keyed to the running thread break in event-loop or coroutine web frameworks?

level: seniorimportance: should knowfreq 46%

basics

~20 s

Thread-keyed storage assumes one thread serves one request end to end. Under loop or coroutine execution a thread interleaves many requests and a request touches several threads, so context goes missing after a resumption, or belongs to somebody else.

open as a page

When one event-loop thread in a web server stalls, which server duties stop, and what limits the blast radius?

level: seniorimportance: should knowfreq 50%

basics

~20 s

A stalled loop stops every duty it owns: reading and writing its sockets, completing handshakes, firing its own timers, often accepting connections. Sockets stay with the loop that accepted them, so the damage covers that loop's share of clients.

open as a page

A client reads a streamed response far slower than the handler produces it — what does the framework do with the unsent data?

level: seniorimportance: should knowfreq 46%

basics

~20 s

A framework does one of three things: the write blocks and pins whatever runs the handler, unsent bytes queue in memory and grow per slow connection, or the write signals not-ready so the handler must pause producing.

open as a page

A web service's request timeout fires on schedule, yet its in-flight count climbs until every worker is busy. How would you diagnose and fix it?

level: seniorimportance: should knowfreq 50%

basics

~20 s

Timed-out handlers are still running. Confirm by comparing in-flight count with response rate and sampling where workers are parked, then bound the slow call itself, propagate cancellation into it, and cap concurrency to that dependency rather than enlarging the pool.

open as a page

Once the response is sent, a failure in the deferred work has no caller to return to — how do you design for that?

level: seniorimportance: should knowfreq 54%

basics

~20 s

Replace the missing caller with three channels: automatic recovery (bounded retry, then a dead-letter place), operator visibility (failure counters, backlog age, a correlation identifier), and a user-facing status or follow-up. And never claim in the response what the deferred work has not yet done.

open as a page

How would you decide which endpoints of a synchronous service are worth converting to awaitable-returning handlers, and which are not?

level: principalimportance: should knowfreq 42%

basics

~20 s

Convert endpoints that hold many concurrent requests whose time is mostly spent waiting on remote calls that can genuinely be awaited. Leave compute-bound endpoints, and those fronting a hard downstream limit, alone: converting them only relocates the queue.

open as a page

How would you choose a thread-per-request, event-loop or coroutine web framework for a new service, and what constrains that choice?

level: principalimportance: should knowfreq 40%

basics

~20 s

Choose from the workload and the ecosystem, not from fashion: connection count and waiting time argue for loop or coroutine models, processor-bound work does not, and blocking client libraries can cancel the benefit entirely. Weigh it as a stack-wide, hard-to-reverse commitment.

open as a page

How would you decide how many minutes-long, held-open streaming responses one instance should carry, and how would you shed them safely?

level: principalimportance: should knowfreq 36%

basics

~10 s

Measure what one open stream costs on the real execution model, find the limit that binds first, cap admission below it, bound every stream's lifetime so the fleet stays drainable, and bound per-stream buffers.

open as a page

How would you decide which post-response work may stay in the serving process and which needs a durable handoff?

level: principalimportance: should knowfreq 44%

basics

~20 s

Decide by the cost of losing an item, not by how long it takes. Best-effort, regenerable work can stay in the process behind bounds; anything the user was told happened, or that a person would have to repair, must be recorded durably before you respond.

open as a page