In a web framework's middleware chain, what happens when a hook throws instead of delegating to the next element?
answer
- nesting, not a queue
- inward side never entered
- unwinds outward, in reverse
- after-code skipped, cleanup still runs
basics
~20 sThe 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 sA 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
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.
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.
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.
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