In a framework's dispatch pipeline, where do an unmatched route, a binding failure, a handler exception and a rendering failure each exit?
answer
- each failure has a last stage reached
- empty handler log means it exited earlier
- rendering fails after the handler returned
- flushed bytes cannot be taken back
- the error stage has no error stage
basics
~20 sAn unmatched route exits at resolution; a binding failure exits before the handler; a handler exception unwinds into the error stage; a rendering failure reaches it too, but may arrive after the first bytes are already on the wire.
solid answer
~50 sEach failure kind has a stage it can no longer get past, and knowing which one explains the evidence you have. **Unmatched route**: dispatch ends at resolution, so no route-scoped hook, no binding and no handler ran — which is why these requests are invisible to per-route instrumentation. **Binding failure**: conversion or declarative validation rejects the input before the handler is entered, so the handler's logs are empty and the response is typically a 4xx. **Handler exception**: it unwinds out of the handler into the **error stage**, which produces a response in place of rendering. **Rendering failure** — a value that cannot be serialized, a template that blows up, a stream that dies mid-body — also diverts to the error stage, but it happens *after* the handler returned successfully, so nothing inside the handler can catch it, and if bytes were already flushed the framework can only truncate or abort the connection.
go deeper
Learn the simplest tell: if the handler's log line is missing, the request exited before the handler — at resolution or at binding. If it is present, the failure came later.
Map each failure kind to the stage it cannot get past, and explain why a handler's own try/catch cannot cover serialization of the value it returned.
Reason about the consequences: committed work behind a failed response, truncated streams, instrumentation blind to unmatched routes, and an error stage that depends on the thing that just failed.
Decide the policy — where units of work are completed relative to writing the response, whether large responses stream at the cost of clean failure, and what the guaranteed minimum behaviour is when the error stage itself is broken.
## Why the exit point is the diagnosis Every failure in a dispatch pipeline has a **last stage it reached**. Because the stages are ordered and each one consumes the previous one's output, the exit point tells you, without a debugger, which evidence should exist and which cannot: whether the handler ran, whether a unit of work was opened, whether route-scoped instrumentation fired, and how much freedom the framework still had to shape the response. Almost every "why is there no log line" question is really a question about the exit point. ## The four exits | Failure | Exits at | Handler ran? | Response the framework can still produce | |---|---|---|---| | No route matched | Route resolution | No | Full freedom; typically 404 | | Input cannot be converted or fails declarative validation | Binding, before invocation | No | Full freedom; typically a 4xx | | Handler raised | Error stage, unwinding out of the handler | Yes, partly | Full freedom, unless the handler already wrote | | Rendering blew up | Error stage, after the handler returned | Yes, fully | Only if nothing has been flushed yet | The last row is the one that separates candidates. The first three exits happen while the response is still entirely in the framework's hands, so the error stage can replace it wholesale. A rendering failure may happen after the status line and headers have gone out, and at that point there is no mechanism in HTTP to take them back. ## Exit one: nothing matched Resolution is the first gate, and failing it is the cheapest exit in the pipeline. Nothing scoped to a route — no route-scoped hook, no binding, no handler — can have run, because none of it had been selected. Two consequences follow. First, dashboards built on route-scoped instrumentation under-report: scanning traffic and typos are invisible to them and must be counted at an outer position. Second, a globally mounted outermost element is the only place inside the framework that sees these requests at all. ## Exit two: the input is not usable Binding turns raw strings and bytes into the handler's typed inputs. A body that is not well-formed, a path variable that is not a number, a missing required field caught by declarative validation — all of these exit **before** invocation. The tell is an empty handler log next to a 4xx in the access log. This is also why defensive checks written as the first lines of a handler are often dead code: the framework already rejected everything they were meant to catch. ## Exit three: the handler raised An exception thrown by application code propagates out of the invocation stage and diverts into the **error stage**, which is where the framework decides what to answer with instead of rendering a result. Two subtleties matter in production. If the handler had already written part of the response directly rather than returning a value, the error stage inherits a partly-written response and its options shrink accordingly. And if the handler's result is asynchronous, the failure may surface after the invocation stack has already returned — the framework still routes it to the error stage, but the code that catches it is no longer on the handler's call stack, so anything relying on stack-local state will not see it. ## Exit four: rendering blew up This is the exit people forget. The handler succeeded and returned a value; serialization, template expansion, or the stream producing the body then failed. Key properties: - **The handler cannot catch it.** It happens after the handler returned, so a try/catch around the handler's own body has nothing to do with it. - **A unit of work opened inbound may already have been committed** by an outbound step before rendering ran, so "the request failed" and "the write happened" are both true. - **If nothing has been flushed**, the error stage can still replace the whole response cleanly. - **If bytes are already on the wire**, it cannot. The framework's only honest moves are to stop writing, leaving a truncated body, or to abort the connection so the client sees a transport error rather than a plausible-but-incomplete payload. That last point is why streaming and large responses deserve extra care: they trade the ability to fail cleanly for lower memory use and earlier first bytes. ## The error stage can fail too The error stage is code, and code raises. A failure inside it has nowhere left to divert to, so frameworks fall back to a minimal built-in response — a bare 500 with no body, or a closed connection. Two habits keep you out of that corner: keep the error stage free of the dependencies that were most likely to have failed in the first place, and make sure resource release is anchored at the request's completion boundary rather than inside the error stage, so that a failure there does not also leak resources.
- Why can a failed request still have committed its database work?Because the unit of work opened on the way in is usually completed on the way out, and it can be completed before rendering runs. If serialization then fails, the write is already durable while the client sees an error. Committing after the response is fully written, or making the operation idempotent and retriable, are the usual answers.
- How do you keep an unmatched-route flood visible if per-route metrics cannot see it?Count at a position that wraps route resolution — an outermost element inside the framework, or the layer in front of it — and label by outcome rather than by matched route. Anything scoped to a route is structurally unable to observe requests that never matched one.
- What is the practical difference between truncating the body and aborting the connection when rendering fails mid-stream?A truncated body with a declared length or a terminated chunked stream lets a careful client detect the mismatch, but a lenient one may accept a partial payload as complete. Aborting is louder and harder to mistake for success, at the cost of a transport-level error the client must interpret.
saying these in an interview costs you the question
- Thinks every failure is caught by the same handler-level error handling.
- Believes a rendering failure can always be turned into a clean 500.
- Says an unmatched path still runs the route's hooks and binding.
- Assumes a failed response means no side effect was committed.
- Expects the error stage itself to be immune to failure.
- Adds validation to the top of a handler that binding already rejected.