skip to content

Interceptor Chains

Composing several handlers around a single call: ordering, proceeding inward, short-circuiting, and rewriting arguments or results. Interviewers ask how ordering changes observable behavior.

on this pageshow

questions

6

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
open as a page

A timing interceptor sits inside the retrying interceptor in one chain and outside it in another - what differs in what each records?

level: seniorimportance: must knowfreq 55%

basics

~20 s

Position changes both how often a handler runs and what interval it covers. Inside the retrying handler, timing is entered once per attempt and records each attempt alone. Outside it, timing is entered once and records every attempt plus the waits between them.

open as a page

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

level: middleimportance: should knowfreq 50%

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.

open as a page

One interceptor in a chain catches the exception the target threw and returns a fallback value - what do the outer handlers see?

level: seniorimportance: should knowfreq 44%

basics

~20 s

An ordinary successful return of the fallback value. Handlers outside the catching one never learn a failure occurred, so their failure paths do not fire, while handlers between it and the target saw the failure travel through and had their after-work skipped.

open as a page

How would you govern ordering when many teams contribute interceptors to one shared chain around every request?

level: principalimportance: should knowfreq 30%

basics

~20 s

Publish ordering as a contract, not a setting: reserved bands for admission, observability and business handlers, relative constraints rather than hand-picked numbers, declared powers for stopping a call or rewriting values, and a resolved order that is printed and asserted in tests.

open as a page

What goes wrong when a handler re-enters the same interceptor chain while that chain is still running?

level: seniorimportance: nice to knowfreq 28%

basics

~20 s

The whole chain runs a second time inside the first run. Per-call accounting is charged twice, measurements nest and sum to more than elapsed time, handler state kept in a single per-handler slot is overwritten, and an unconditional re-entry recurses without bound.

open as a page