skip to content

Middleware, Filters & Interceptors

How cross-cutting hooks wrap a handler as a chain, and how order, scope and thrown errors decide what runs. Interviewers ask because a misplaced hook is a silent security bug.

on this pageshow

explore

questions

29

In a web framework's middleware chain, what happens when a hook throws instead of delegating to the next element?

level: juniorimportance: must knowfreq 68%

answer

  1. nesting, not a queue
  2. inward side never entered
  3. unwinds outward, in reverse
  4. after-code skipped, cleanup still runs

basics

~20 s

The chain stops there: nothing inward of that hook is entered, so the handler never runs. The error unwinds outward through the hooks already entered, in reverse, and the outermost error stage turns it into a response.

solid answer

~50 s

A middleware chain is a stack of nested calls, not a queue of independent steps. Each element does some work, calls the next element (which is the whole remainder of the chain), and regains control when that call finishes. If a hook throws before delegating, everything inward of it — the remaining hooks and the handler — is never entered, so none of their work happens. The failure then travels outward through the hooks that are still on the stack, in the reverse of the order they were entered, so the first-registered hook is typically the last to see it. Being on the stack is not the same as observing the error, though: plain statements after the delegate call are skipped, and only a cleanup construct or a catch around that call participates in the failure path.

go deeper

for a junior

Recall the shape: a hook that throws stops the request at that point, the handler never runs, and the framework answers with a failure response instead of the handler's output.

for a middle

Explain the unwinding itself: the inward side is skipped, the outward side is traversed in reverse entry order, and only code that explicitly catches or cleans up takes part.

for a senior

Show that you reason about half-applied work — what a hook acquired before it threw, and whether the code that releases it is actually on the unwind path.

for a principal

Frame it as a contract across teams: which layer may fail loudly, which must always release what it acquired, and how that expectation is made checkable rather than cultural.

A middleware chain is assembled by nesting, not by queueing. Each element is handed the request plus one callable representing **the entire rest of the chain**, and the innermost callable is the route handler. An element does some work, delegates, and regains control when the inner part finishes. Because delegation is an ordinary call, the chain is a call stack — and that single fact explains everything that happens when something throws. ## Two directions, not one Picture the chain as rings around the handler. A request travels inward through each ring, the handler runs, and the result travels back outward through the same rings in reverse. When a hook throws instead of delegating, the request stops travelling inward at that ring. - **The inward side is never entered.** The hooks that would have run after it, and the handler itself, are not called. Whatever they do — body parsing, argument binding, the business work — simply does not happen, and nothing there can catch up later. - **The outward side is already on the stack.** Every hook the request passed through to reach this one has an unfinished delegate call in its frame. The failure unwinds through those frames in the reverse of the order they were entered. Reverse order is the part candidates most often get backwards. The hook registered first is usually entered first, which makes it the **last** one the error passes through on the way out. The error stage, placed outermost precisely so that it wraps everything, is therefore the final stop rather than the first. ## Being on the stack is not the same as seeing the error An enclosing hook participates in the unwind, but that does not mean its code runs. What runs depends on how its code is written around the delegate call. | Code in the enclosing hook | Runs when the inner part throws? | Typical use | |---|---|---| | Plain statements after the delegate call | No — the unwind skips them | after-phase work that assumes success | | A cleanup construct wrapping the call | Yes, on both paths | releasing a connection, clearing ambient context | | A catch wrapping the call | Yes, and it decides what happens next | recording, enriching, translating, recovering | This is why a hook written as "record the start time, delegate, record the finish time" silently loses every failed request: the finish line lives on the success path only. ## Where the throw happened still matters From the client's side a failure looks similar wherever it came from, but the amount of work already done is very different. 1. **A hook throws before delegating.** Nothing inward ran. The handler never executed, so no business side effect can exist yet — only whatever that hook and its enclosers already did. 2. **The handler throws.** Every entered hook's before-phase ran and the handler ran partway, so side effects it already performed remain; the outbound half of the chain is lost. 3. **A hook throws after its delegate call returned.** The handler completed successfully and the failure is in the outbound work, leaving a state where the work is done but the caller is told it failed. Distinguishing these is what makes a failure diagnosable afterwards, which is why a good error stage records not only the error but which elements were entered. ## Frameworks differ in how the failure is represented Some frameworks propagate failures as thrown exceptions unwinding a real call stack; others model the chain over a value representing eventual completion, where the failure is carried in that value and the unwind is a composition of failure paths; a few use an explicit error-carrying return type that every element passes along. The vocabulary changes — a raise, a failed completion, an error branch — but the structural rule does not: the inward side that was never entered does nothing, and the outward side reacts only where it explicitly handles the failure path. The models do differ in one respect worth knowing: with thrown exceptions propagation is automatic, while with error-carrying values an element that forgets to pass the failure along can drop it silently. ## What to take into a review - Assume any hook may throw, and ask what the enclosing hooks have already acquired. - Put anything that must happen on both paths in a cleanup construct, never in the statements after the delegate call. - Never write code that assumes the handler ran just because the request reached the chain. - Remember that the outermost element is the last to see the failure, and design it to be the place that shapes the answer.

  • Does the hook that threw still run its own code after the delegate call?
    No. The throw leaves that hook abnormally as well, so the statements following the delegate call in the same hook are skipped just like the inner elements. Only a cleanup construct in that hook still runs. Anything it must undo has to live in the cleanup path, not in the lines after the call.
  • In what order do the enclosing hooks see the failure?
    Reverse of entry. The chain is entered outermost-first, so the most recently entered hook unwinds first and the outermost element sees it last. Registration order therefore reads forward for the request and backward for the failure, which is why the error stage is placed at the outer edge.
  • Does it matter whether the hook threw before or after it delegated?
    Yes, for what already happened. Throwing before delegating means the handler never ran, so no business side effect exists. Throwing after the delegate call returned means the handler completed and its effects stand, while only the outbound work is lost. The two are indistinguishable to the client, so the error record should say which it was.

It is like a set of doors you walk through: a failure at the fourth door means you never reach the fifth, and you leave by pushing back out through the three you already opened.

saying these in an interview costs you the question

  • Says the remaining inner hooks still run after one throws
  • Assumes the handler always executes once a request enters the chain
  • Thinks every registered hook observes the error, including ones never entered
  • Expects statements written after the delegate call to run on the failure path
  • Believes the first-registered hook is the first to see the failure
open as a page

In a web framework's middleware chain, what does each element wrap, and what must it do for the request to continue?

level: juniorimportance: must knowfreq 72%

basics

~20 s

Each element wraps the entire remainder of the chain as one callable. It receives the request, may act on it, then explicitly invokes that remainder; if it never invokes it, the request never reaches the handler.

open as a page

In a web framework's middleware chain, why is the correlation-id hook placed first, ahead of logging and authentication?

level: juniorimportance: must knowfreq 66%

basics

~20 s

A correlation id must exist before anything downstream writes a log line, records a metric or calls another service, so the hook that adopts or generates it runs first. Anything registered ahead of it emits records nothing can join.

open as a page

In a web framework, what can a hook that runs before routing see, and what can it not yet know?

level: juniorimportance: must knowfreq 66%

basics

~20 s

A hook running before routing sees the raw message and the connection: method, request-target string, headers, client address, an unread body. The route template, path variables, handler, bound arguments and return value do not exist yet.

open as a page

In a middleware chain, how does the order in which hooks are registered decide the order they run in?

level: juniorimportance: must knowfreq 74%

basics

~10 s

By default, registration order is inbound execution order: the first hook registered becomes the outermost layer and runs first, each one delegating inward, with the matched route handler innermost and running last.

open as a page

In a web framework, what changes when a hook is attached globally rather than to one route or group?

level: juniorimportance: must knowfreq 72%

basics

~20 s

Scope decides which requests enter the hook. A globally attached hook runs for every request the server accepts, including ones that match no route; a route- or group-scoped hook runs only for requests whose matched route sits inside that scope.

open as a page

In a middleware chain, why is a hook's after-delegate code skipped when an inner element throws?

level: middleimportance: must knowfreq 58%

basics

~20 s

Because the delegate call never returned normally: the unwind jumps past the statements that follow it. Only a cleanup construct around the call still runs, and only a catch lets the hook observe the failure at all.

open as a page

In a web framework's middleware chain, what happens if an element never invokes the rest of the chain, and what if it invokes it twice?

level: middleimportance: must knowfreq 60%

basics

~20 s

Skipping the call removes the handler and everything after it: the client gets a default answer or waits for a timeout. Invoking twice runs the whole remainder twice, duplicating side effects while the client still sees one response.

open as a page

Where in a middleware chain do access logging and request metrics belong, and what does placing them too deep hide?

level: middleimportance: must knowfreq 62%

basics

~20 s

Access logging and request metrics belong at the outer edge of the chain, just inside the correlation-id hook, so they observe every request — including those an inner hook rejects — and time the whole pipeline, not just the handler.

open as a page

How do the server-edge, pipeline, and handler-aware hook altitudes in a web framework differ in what they can observe?

level: middleimportance: must knowfreq 72%

basics

~20 s

Each altitude sees more application meaning and less of the wire: the server edge sees connection facts and raw bytes, a pipeline hook the framework's request and response objects plus the route, a handler-aware hook the bound arguments and return value.

open as a page

What happens to the rest of a middleware chain when a hook answers a request itself instead of delegating onward?

level: middleimportance: must knowfreq 66%

basics

~20 s

The chain ends there: every hook nested inside and the route handler are skipped entirely. The hooks already entered around it still resume normally, so their after-phases run and can log, time or decorate that early response.

open as a page

How do you keep an authentication hook off a health-check endpoint without leaving other endpoints unprotected?

level: seniorimportance: must knowfreq 66%

basics

~20 s

Carve the exemption out structurally: register the public endpoints in their own scope and attach authentication to the protected scope. If an exclusion list is unavoidable, match the routed identity exactly rather than a raw path prefix, and test the exempt set.

open as a page

In a web framework's middleware chain, what work belongs before the delegation call and what belongs after it returns?

level: middleimportance: should knowfreq 62%

basics

~20 s

Before the call only the request exists, so that phase validates, tags, adjusts and starts timers. After it returns the response exists, so that phase logs status and size and may change headers only if nothing has been sent.

open as a page

In a middleware chain, why must the CORS hook sit ahead of authentication rather than behind it?

level: middleimportance: should knowfreq 55%

basics

~20 s

A browser sends the preflight without credentials, so an authentication hook placed ahead of the CORS hook answers it with a 401 carrying no CORS headers, and the browser blocks the real request. CORS must run first.

open as a page

In a middleware chain, why do after-phases run in reverse registration order, and which hook touches the response last?

level: middleimportance: should knowfreq 58%

basics

~20 s

Each hook wraps the rest of the chain, so control returns inside-out: the innermost hook sees the response first and the first-registered, outermost hook sees it last, making outbound order the mirror of inbound order.

open as a page

Which requests does a hook attached before routing run for that a route-scoped hook never sees?

level: middleimportance: should knowfreq 54%

basics

~20 s

Everything the server accepts but never routes: paths matching no route, methods the matched path does not support, requests rejected for a malformed line or oversized body, framework-handled redirects and preflight replies, and anything a wider hook answered before routing happened.

open as a page

When a global hook and a route-scoped hook both apply to a request, what decides which one wraps the other?

level: middleimportance: should knowfreq 50%

basics

~20 s

Nesting usually decides, not registration order. The chain is assembled from the outside in, so the wider scope's hooks wrap the narrower scope's, and registration order only breaks ties within one scope. Some frameworks add explicit priorities that can cut across scopes.

open as a page

In a web framework, why can an error inside work a handler scheduled to outlive it escape the middleware chain?

level: seniorimportance: should knowfreq 42%

basics

~20 s

A chain can only unwind a call that is still in progress. Detached work finishes after the chain has already returned, so no frame is left to unwind into and no element in the request path ever sees the failure.

open as a page

In a web framework, why can an error thrown by a hook registered outside the error-handling stage escape it?

level: seniorimportance: should knowfreq 52%

basics

~20 s

Because the error stage is itself a chain element whose reach is exactly what it encloses. A hook registered outside it is never inside its catch, so anything that hook throws unwinds straight past it into the framework's last-resort path.

open as a page

In a web framework's middleware chain, when does an element mutate the request in place versus pass a replacement to the next element?

level: seniorimportance: should knowfreq 52%

basics

~20 s

Mutate in place to attach something nobody else owns, such as a correlation value. Pass a replacement when overriding what the handler reads or wrapping the response, and remember a replacement is visible only to what you delegate with.

open as a page

Why do services split rate limiting into a pre-auth hook keyed by client address and a post-auth hook keyed by principal?

level: seniorimportance: should knowfreq 58%

basics

~20 s

Before authentication the only key available is the network address, and that tier exists to protect the credential-checking path itself. After authentication the limiter can key by principal and enforce the per-account quota the product actually promises.

open as a page

Why do hooks that wrap a resolved handler never observe some requests the server actually received?

level: seniorimportance: should knowfreq 54%

basics

~20 s

A handler-aware hook runs only when a handler is invoked, so anything refused earlier — no route match, wrong method, unacceptable media type, a body that failed to convert, an outer credential check — is answered without ever reaching it.

open as a page

Middleware hooks are registered from several startup modules — how do you pin down the effective chain order and break ties?

level: seniorimportance: should knowfreq 46%

basics

~20 s

Find the framework's ordering rule first — explicit order values, then named phases, then registration sequence — and make ordering-sensitive hooks state their position explicitly, because equal-ranked hooks fall back to incidental discovery order that can change without warning.

open as a page

Which cross-cutting concerns would you keep in the application's middleware chain rather than push to a shared edge layer?

level: principalimportance: should knowfreq 40%

basics

~20 s

Keep in the chain whatever needs application knowledge: the matched route, the principal, the tenant, the meaning of a failure. Push to the edge what is uniform and must act before traffic arrives. A few concerns belong in both.

open as a page

A policy needs a fact captured at the server edge and an operation identity that only routing produces — where should it decide?

level: principalimportance: should knowfreq 44%

basics

~20 s

The decision must run at or below the altitude of the latest fact it needs: capture the connection fact at the outermost hook into per-request state, then decide deeper, where the operation identity exists. Facts travel inward, never outward.

open as a page

When a middleware chain's delegation call returns a pending result rather than a finished response, where does the after phase belong?

level: seniorimportance: nice to knowfreq 44%

basics

~20 s

Attach it to the completion of the pending result and return the composed result outward. Code written as the next statement runs while the inner work is still in flight, so it sees no response and releases resources too early.

open as a page

Where does response compression belong in a middleware chain, and what breaks when it is installed too deep?

level: seniorimportance: nice to knowfreq 42%

basics

~20 s

Compression wraps the response on its way out, so it belongs outside every element that can produce a body — handler, error stage and static assets alike. Placed too deep it silently misses responses produced outside it.

open as a page

How would you decide which hooks in a shared middleware chain may recover from errors rather than let them unwind?

level: principalimportance: nice to knowfreq 33%

basics

~20 s

Default to one place that turns failures into responses and let every other hook observe and rethrow. Permit recovery only where the concern is optional, the degraded outcome is defined, and the recovery is counted rather than silent.

open as a page

As a service grows to many endpoint families, how would you structure middleware scopes so the applied policy stays auditable?

level: principalimportance: nice to knowfreq 42%

basics

~20 s

Branch the route tree by caller audience rather than by feature, give each branch one explicit chain, and reserve predicates inside hooks for genuinely runtime conditions. The hooks running for an endpoint should be readable from its registration.

open as a page