skip to content

When a request passes through a chain of interceptors around one call, in what order do their after-the-call parts run?

level: middleimportance: must knowfreq 62%

answer

  1. handlers nest, they do not queue
  2. one open frame per handler
  3. inward first, outward afterwards
  4. last entered is first to resume
  5. after-parts mirror the entry order

basics

~20 s

In reverse of the order they were entered. Handlers nest rather than queue: each one calls inward and waits, so the handler entered last is the first to resume, and the outermost handler finishes last.

solid answer

~40 s

An interceptor chain is not a list processed top to bottom; it is a nesting. Assembling it gives each handler a reference to the rest of the chain, ending at the target. A handler does work, makes an inward call to continue, and then does more work with whatever came back. So the before-parts run outermost-inward, and the after-parts run innermost-outward, the reverse. If the chain is authentication, quota, timing, retry, target, the target returns first, then retry's after-work, then timing's, then quota's, then authentication's. Each handler in front of the target holds an open frame while the target runs, which is why a handler's position decides both when it runs and what value it is looking at.

code

pseudocode · 12 lines
pseudocode
function timingHandler(call, next):
    start = now()
    result = next(call)          // pass control inward, then wait
    record(now() - start)        // runs while the chain unwinds
    return result

chain = wrap(authHandler,
          wrap(quotaHandler,
            wrap(timingHandler, target)))

// entering:   auth -> quota -> timing -> target
// returning:  target -> timing -> quota -> auth

go deeper

for a junior

Recall the shape: a handler can run work before the call and work after it, and it must pass control inward for the call to continue at all.

for a middle

Explain the nesting: entry runs outermost-inward, return runs innermost-outward, and every handler in front of the target holds an open frame while the target runs.

for a senior

Show that you use the ordering to predict behaviour - which handler sees which value, how many times an inner handler is entered, and where a translation must sit to be visible to the caller.

for a principal

Treat the nesting as a contract the platform publishes, so that teams adding handlers can reason about position without reading each other's code.

## Handlers nest; they do not queue An **interceptor chain** is a set of handlers placed around a single call so that each may run work before the call reaches its **target**, work after a result comes back, or both. The common wrong picture is a list processed top to bottom. It is a **nesting**: assembling the chain hands each handler a reference to the rest of the chain, and the innermost reference is the target itself. A wrapping handler therefore has four moments, always in this order: 1. work before it passes control inward; 2. the inward call, which runs everything nested inside it; 3. work after that call returns, using whatever came back; 4. the value it hands outward to the handler that called it. A handler that has only the first moment is a pure pre-step. One that has only the third is a pure post-step. One that has both genuinely *wraps* the call, and only that shape can measure, retry, or replace what happened inside. ## Entry is outermost-inward; return is innermost-outward Take a chain of four handlers around one target: authentication, quota, timing, retry. Entering, the before-parts fire in assembly order - authentication, quota, timing, retry - and then the target runs. Returning, the after-parts fire in the exact reverse - retry, timing, quota, authentication - because each handler resumes only when its own inward call has come back. With `n` handlers in front of the target, `n` frames are open while the target executes, one per handler that has proceeded and not yet resumed. That is the whole mechanism; everything else follows from it. | moment | runs in | what the handler still controls there | |---|---|---| | before proceeding | assembly order, outermost first | the arguments it passes inward, and whether the call continues at all | | after proceeding | reverse order, innermost first | the value it hands outward, and whether a failure keeps travelling | ## What position therefore decides - A handler sees the target's own return value only when no handler nested inside it replaced that value first. - A handler placed outside a retrying handler is entered once per request; one placed inside it is entered once per attempt. - After-work runs only when control actually returns through that handler, which is why a short-circuit or a failure can skip it. - Two handlers that both log will interleave as a matched pair of brackets, not as two independent sequences. - Reordering two handlers changes both phases at once: swapping their before-parts necessarily swaps their after-parts the other way. - The outermost handler is the last thing the caller's result passes through, which is what makes it the right place for a translation the caller must see. ## Three shapes that bend the picture - **A handler that never proceeds.** Nothing nested inside it runs at all, yet the handlers outside it already ran their before-parts and still run their after-parts, seeing the substituted value as though the target had produced it. - **A failure instead of a value.** The unwind still travels outward, but a handler's after-work is skipped unless that handler explicitly deals with the failure, so the ordering rule holds while the work each handler does changes. - **A result that has not completed yet.** If the inward call hands back a pending result rather than a finished one, work written after the inward call runs when the chain was *assembled through*, not when the operation finished. Some runtimes give you a way to attach the after-work to completion instead; the point is that the frame-based ordering above describes a synchronous return, and a pending result must be handled deliberately. ## How to answer it in an interview Say the three things in order: handlers nest, entry runs outermost-inward, return runs innermost-outward. Then give one consequence you have actually seen - a duration that turned out to cover more than you thought, a log line that appeared after another you expected it to precede, a value the outer handler saw that no longer matched what the target returned. Interviewers ask this because almost every framework behaviour that looks like magic - transactional boundaries, request logging, quota accounting, latency metrics - is one nesting decision, and a candidate who can draw the nesting can predict all of them without reading that framework's manual.

  • A handler replaces the value it returns after proceeding. Which handlers in the chain see the replacement?
    Only the handlers outside it. Everything nested inside has already returned and can never observe the substitution. This is why a handler that translates or redacts a result must sit outside every handler that is meant to see the translated form, and inside none of the ones that must audit the original.
  • Why does a handler that only runs work before proceeding not need to be in the chain at all in some designs?
    Because a pure pre-step influences nothing after the call, it can be expressed as a plain call made before entering the chain. Keeping it as a handler buys uniform registration and ordering with the wrapping handlers; that is a packaging choice, not a behavioural need.
  • If the inward call hands back a result that has not completed yet, where does the after-work belong?
    Attached to the completion of that result, not written after the inward call. Code written after the inward call runs when the chain finished assembling the operation, so a duration measured there records assembly time, not the time the operation actually took.

Walking into a building through a series of doors: on a normal way out you meet them again in the opposite order to the one you entered by.

saying these in an interview costs you the question

  • Describes handlers as a flat queue processed top to bottom
  • Says the after-parts run in the same order as the before-parts
  • Claims the outermost handler always sees the target's own return value
  • Treats handler order as a cosmetic registration detail
  • Assumes a handler's after-work runs even when control never returns through it
  • Thinks only the first handler can change what the caller receives