When a pre-handler hook short-circuits a request in a framework's dispatch pipeline, which stages still run and which are skipped?
answer
- skipped forward, unwound backward
- only elements already entered come back
- the handler is never reached
- completion callbacks are the reliable anchor
- outermost sees rejected traffic too
basics
~20 sEverything downstream is skipped: the remaining hooks, binding, the handler and normal rendering. The hooks that already ran still get control back on the way out, and the response the short-circuiting hook produced is still written before the request's resources are released.
solid answer
~50 sA short-circuit means an element of the hook chain produces a response instead of passing the request on. From that point **forward**, nothing runs: no later hook, no binding, no handler, and no rendering of a handler result. **Backward** is different — every element that had already been entered still regains control, so an outermost timing or access-log hook still records the request and still sees the status. Cleanup tied to the request's lifetime still runs. The part that varies is elements registered *after* the exit point: in a model where each element wraps the rest of the chain they were never entered, so they cannot run, while a framework offering separately registered after-callbacks may run some of them anyway. If a step must run for every request however it ended, attach it to the request's completion rather than placing it late in the chain.
go deeper
Remember that an early response means your handler never ran. If you are looking for a log line the handler writes and it is absent, the request probably ended before reaching it.
Explain the wrapping model: elements nest, so an early exit returns through everything already entered and skips everything not yet entered. Be precise about which side of the exit each element sits on.
Design for it — move must-run work to a completion boundary, mount observability outermost, and close in the outbound half whatever you opened in the inbound half. Expect rejected traffic to dominate some endpoints.
Decide where the platform's mandatory elements sit relative to each team's own, knowing that outermost buys uniform coverage at the price of detail, and that a guarantee expressed as 'a hook late in the chain' is not a guarantee at all.
## What short-circuiting is The hook chain of a dispatch pipeline is built so that each element has a choice: **pass the request along** to the rest of the pipeline, or **answer it now**. The second choice is short-circuiting. An authentication element that finds no credential writes 401; a rate limiter over budget writes 429; a cache element with a fresh entry writes 200 with the stored body. In each case the pipeline's forward motion stops at that element. This is not an error path. A short-circuit is a normal, designed exit — which is exactly why it surprises people: the request produced a perfectly good response without the handler ever running, so handler-level logging, handler-level metrics and handler-level cleanup all saw nothing. ## What is skipped and what still runs | | Runs? | Why | |---|---|---| | Hooks already entered, on the way out | Yes | They are mid-call; the short-circuiting element returns into them | | Hooks after the exit point | No, in a wrapping model | They were never entered, so they have nothing to return from | | Binding | No | It sits behind the rest of the chain | | The handler | No | The whole point of the exit | | Rendering of a handler result | No | There is no handler result; the short-circuit's own response is written instead | | Writing the response and releasing resources | Yes | The request still has to be answered and closed | The single sentence worth memorising: **forward stages are skipped, entered stages are unwound.** ## Why the outbound half still fires In the common model, an element does not merely "run before the handler" — it *wraps* everything after it. Think of the chain as nested calls rather than a list of steps. If element A wraps B wraps the handler, and B short-circuits, then B is returning a response to A exactly as it would have if the handler had produced it. A cannot tell the difference except by looking at the status. That is a feature: it is why a metrics element mounted outermost counts rejected requests, and why a correlation-id element still stamps the response header of a 401. It is also why ordering decides observability. Anything mounted *inside* the element that rejects the request will neither see the request nor the response. ## Where the model varies Frameworks differ in how the two halves are expressed. Some make you write one function that calls the next element and regains control when it returns, so inbound and outbound work are inseparable and the unwinding rules above hold exactly. Others let you register a before-callback and an after-callback independently; there the framework has to decide which after-callbacks to run for a request that exited early, and the answers range from "only those whose before-callback ran" to "all of them" to "only those marked as always-run". Read your framework's rule once rather than assuming; it is one of the few places where a plausible assumption is genuinely coin-flip. ## Designing for it 1. **Put must-always-run work at the completion boundary.** Most frameworks expose a per-request completion callback, or an always-run wrapper position, that fires however dispatch ended — normal response, short-circuit, or error. That is the reliable anchor for access logs, metrics and resource release. 2. **Put broad observability outermost.** Mounted outermost, a timing element measures everything including rejections; mounted innermost it measures only the requests that survived every gate. 3. **Do not lean on the handler as the place that records the request.** A meaningful share of production traffic — unauthenticated calls, rate-limited bursts, cached hits — never reaches it, so handler-side counters systematically under-report. 4. **Release what you acquired on the way in, on the way out.** An element that opens a unit of work must close it in its own outbound half, because the stage that would normally have closed it may never run. ## The symptom this explains "My after-hook did not run" and "my metrics are missing exactly the interesting requests" are the same bug seen from two angles. Both are answered by asking where the request exited and which elements had already been entered at that moment. If the exit happened before your element was entered, no amount of configuration inside that element will make it fire — it has to be moved outward, or replaced by a completion callback.
- If an authorization hook answers 403, does that response still travel through the outbound half?Yes, through the part of it that had already been entered. Elements mounted outside the authorization hook regain control and can observe the 403, add response headers and record timings. Elements mounted inside it were never reached and see nothing, which is why they are the wrong place for access logging.
- A cache hook answers from its stored copy. Which pipeline stages did that response skip, and what does that imply for tests?It skipped binding, the handler and rendering, so nothing downstream shaped the bytes. Tests that assert serialization behaviour will not exercise it on a cache hit, and a change to the rendering step will not be reflected in cached responses until they are invalidated.
- Why is 'put the log line at the end of the chain' a fragile design?Because the end of the chain is the position least likely to be reached: every gate in front of it can exit first. Logging belongs either outermost, where it wraps every exit, or in a completion callback that the framework invokes however dispatch finished.
saying these in an interview costs you the question
- Says a short-circuit skips cleanup and the outbound half entirely.
- Believes the handler still runs and its result is quietly discarded.
- Assumes every registered after-callback runs no matter where the exit happened.
- Expects elements mounted after the exit point to still observe the request.
- Counts requests in the handler and calls that total request volume.