skip to content

What happens to the rest of an interceptor chain when one handler returns a result without passing control inward?

level: middleimportance: should knowfreq 50%

answer

  1. continuing is a choice, not a default
  2. inside collapses, outside is untouched
  3. the target is never invoked
  4. outer after-parts still run normally
  5. substituted value must honour the contract

basics

~20 s

Everything nested inside that handler is skipped - the inner handlers and the target never run. The handlers outside it already ran their before-parts and still run their after-parts, and they see the substituted value as though the target had produced it.

solid answer

~40 s

Short-circuiting means a handler produces the outward value itself instead of proceeding. The nesting inside it collapses: no inner handler is entered, the target is never invoked, and none of the effects those would have produced happen. Outward, nothing looks unusual - the handlers already on the stack resume in the usual reverse order and receive a perfectly ordinary result, indistinguishable from one the target returned unless the handler marks it. That is exactly what makes an admission check, a rejection, or a stored-answer handler work. It also means any counter, log or measurement placed inside a handler that can stop the call records only the requests that got past it, which is a frequent source of numbers that silently disagree.

code

pseudocode · 9 lines
pseudocode
function quotaHandler(call, next):
    if budgetRemaining(call.owner) <= 0:
        return refusal("budget exhausted")   // never proceeds
    charge(call.owner)
    return next(call)

// chain: auth -> quota -> timing -> target
// on refusal: timing never entered, target never invoked,
//             auth's after-part still runs on the refusal value

go deeper

for a junior

Recall that a handler continues the call only by passing control inward, and that returning instead means the target never runs.

for a middle

Explain the asymmetry: the nesting inside collapses entirely, while the handlers outside resume as usual on the substituted value.

for a senior

Point at the operational consequence - conditional metrics, skipped effects, and substituted results that nothing marks as substituted - and say how you would make them visible.

for a principal

Decide which handlers in a shared chain are permitted to stop a call at all, since that power silently disables every policy nested inside them.

## What short-circuiting is In a chain of handlers wrapped around one call, continuing is a *choice*: a handler continues by making an inward call, and stops by returning a value of its own instead. **Short-circuiting** is the second case. Nothing forces a handler to proceed, and nothing downstream is notified that it did not. This is not an exotic case - it is how most useful interception earns its place. An admission or authentication handler rejects without letting the target see the request. A handler holding a previously computed answer returns it rather than recomputing. A handler enforcing a quota refuses once the budget is spent. A circuit-style handler refuses while a dependency is known to be failing. ## Who runs, and who does not With a chain of authentication, quota, timing, target, suppose the quota handler stops the call: 1. Authentication's before-part has already run; its frame is open. 2. Quota's before-part runs and decides to stop. 3. Timing is **never entered** - neither its before-part nor its after-part. 4. The target is **never invoked**, so none of the effects it would have produced happen. 5. Quota returns a refusal value outward. 6. Authentication's after-part runs normally on that value, exactly as it would have on a real result. The asymmetry is the whole point: **inside is skipped entirely; outside is unaffected in shape**, because those handlers were already suspended at their own inward calls and simply resume. ## Stopping is not the same as replacing Two different powers are easy to conflate: | | short-circuit | proceed, then replace the result | |---|---|---| | does the target run | no | yes | | do nested handlers run | no | yes, both phases | | are the target's effects produced | no | yes, they already happened | | what the outer handlers see | the substituted value | the substituted value | | typical use | admission, refusal, a stored answer | translation, redaction, enrichment | They look identical from outside the stopping handler and are completely different inside it. A handler that must suppress an effect has to stop *before* it; a handler that only needs to reshape what the caller receives can proceed and rewrite on the way out. ## The contract the substituted value must satisfy Whatever a stopping handler returns travels outward as if the target had produced it, so it has to satisfy the same expectations: the same shape the caller was promised, and any invariant the outer handlers rely on. A handler that returns an empty stand-in value where the caller's contract promised a fully populated one moves a failure from the chain, where it is diagnosable, to somewhere far away in the caller. The same applies to a refusal: if the contract's way of saying no is a distinct outcome rather than a value, the stopping handler must produce that outcome, not a value that merely looks empty. ## Failure modes worth naming - **The counting gap.** A counter nested inside a stopping handler counts only the calls that got through, so requests and target invocations legitimately disagree. Decide which number you meant and place the counter accordingly. - **Effects skipped that someone depended on.** Any effect produced inside the stopping handler - including bookkeeping the target performed - does not happen. Effects produced *outside* it still do. - **Two stoppers in one chain.** Only the outermost one that decides to stop has any say; a stopping handler nested inside it never runs, so its policy silently never applies. - **Indistinguishable substitutes.** If nothing marks a value as substituted, outer handlers cannot tell a stored answer from a fresh one, which makes correctness arguments about staleness impossible to state. - **Stopping after an effect already ran.** A handler that produces an effect in its before-part and then stops has done half the work of a call that never happened. ## How to answer it well Name the asymmetry first - inside collapses, outside is unaffected - then say the consequence that matters in production: the substituted value is indistinguishable from a real one unless you make it distinguishable, and every measurement nested inside a handler that can stop the call is conditional on that handler's decision.

  • Two handlers in one chain can both stop the call. Which one's policy actually applies?
    The outer one's, whenever it decides to stop, because the inner one is never entered and cannot express an opinion. Only requests the outer handler lets through reach the inner policy at all, so nesting order, not registration order alone, determines precedence.
  • How would you let the handlers outside a stopping handler tell a substituted result from a real one?
    Carry a marker outward alongside the value - a flag on the result, a field on the per-call context, or a distinct outcome type. Without one, outer handlers cannot attribute latency, count target invocations, or reason about staleness, because the two cases are identical to them.
  • A metric nested inside a handler that can stop the call reports far fewer events than the request count. Is that a bug?
    Not necessarily - it is the definition of that position. The metric counts calls that got past the stopping handler. It is a bug only if you meant to count requests, in which case the handler belongs outside the one that stops them.

saying these in an interview costs you the question

  • Thinks the handlers outside a stopping handler are skipped too
  • Believes the target still runs and its result is simply discarded
  • Assumes outer handlers can always tell a substituted result apart
  • Says a handler must proceed and may only change the result
  • Claims an inner stopping handler overrides an outer one's decision