One interceptor in a chain catches the exception the target threw and returns a fallback value - what do the outer handlers see?
answer
- two unwinds: value or failure
- after-work skipped on the failure path
- catching changes the unwind's shape
- observers are the handlers nested inside
- outer handlers record a slow success
basics
~20 sAn 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.
solid answer
~40 sA chain unwinds in the same outward order whether it carries a value or a failure, but the work each handler does differs. When the target fails, the failure travels outward through the nested handlers; each one's ordinary after-work is skipped unless that handler deals with the failure explicitly. The moment one handler catches it and returns a fallback, the unwind becomes a normal return for everything outside that point. Those outer handlers record a success, their error counters stay flat, and their measurements include the failed work. So the position of the catching handler decides who observes the failure at all: nested handlers see it, outer handlers see a value. Place it too far out and nothing meaningful is ever recorded as failing.
code
pseudocode · 10 linesfunction fallbackHandler(call, next):
attempt:
return next(call)
on failure error:
record(error) // the only place both facts are visible
return defaultResult(call) // outward, this is a normal return
// chain: metrics -> fallback -> retry -> target
// metrics sees: success, with the failed attempts inside its duration
// retry sees: the failure, and may try again before fallback ever runsgo deeper
Recall that a failure travels back out through the handlers and that a handler does its normal after-work only when a value comes back.
Explain that catching converts the unwind: nested handlers see a failure, outer handlers see an ordinary return of the fallback.
Show what that does to operations - failure rates measured outside the catching handler are structurally near zero, while latency still carries the failed work.
Set where fallbacks may be introduced at all, since each one silently defines the boundary beyond which the platform reports success.
## A chain unwinds two ways Control leaves the target in one of two shapes: a **value**, or a **failure**. The direction is the same in both cases - innermost outward, through each suspended handler in reverse of entry - but what each handler gets to do differs sharply. - On a value, each handler's after-work runs with that value and may replace it before passing it outward. - On a failure, each handler's ordinary after-work is **skipped**: the handler never reaches the code written after its inward call. Only work the handler explicitly attached to the failure path runs. That is why a naive measuring handler that records a duration after its inward call records nothing at all for the calls that failed - exactly the calls you most wanted measured. The fix is to attach the recording to both paths, not to hope the value path covers them. ## Catching converts the unwind A handler that catches the failure and returns a fallback does something stronger than logging it: it **changes the shape of the unwind from that point outward**. From the catching handler's own frame outward, the chain is carrying a value again. | position relative to the catching handler | what it observes | |---|---| | nested inside it (between it and the target) | the failure travelling outward; ordinary after-work skipped | | the catching handler itself | the failure, and the decision to replace it | | outside it | a normal successful return of the fallback value | This is the whole answer to the question, and it has a consequence people discover in incidents rather than in review: **the observers of a failure are exactly the handlers nested inside the point where it was caught.** Everything outside is reporting on a system that, from where it stands, worked. ## What that costs, concretely - **Failure rate is measured at a position, not globally.** A rate computed by a handler outside the catching one is the rate of *unhandled* failures, which can be zero while the target fails constantly. - **Latency is still charged.** The time spent failing - and, if a retrying handler sits inside, every failed attempt and every wait - is inside the outer handler's open frame, so the request looks slow but successful. - **The fallback must be true.** A substituted value travels outward indistinguishable from a real one, so it must satisfy the caller's contract and must not be a fabrication the caller will treat as fact. - **Retry interacts with catching by nesting order.** A catching handler nested inside a retrying one makes every attempt look successful, so the retrying handler never retries. Nested outside it, it sees only the final failure after all attempts are spent. - **A translated failure is a different failure outward.** A handler that catches one failure and raises a differently named one keeps the unwind a failure, but every handler outside it now attributes the problem to the new name. ## Where to put the catching handler The useful rule is to place it at the boundary where the fallback is genuinely *equivalent* for the caller, and no further out. Nested too deep, it hides a failure from handlers that needed to act on it - a retrying handler that would have recovered, or a circuit-style handler that needed the failure signal to trip. Placed too far out, it converts every distinguishable failure inside it into one undifferentiated fallback, and the chain stops producing any information about what actually went wrong. Whichever position you pick, record the failure at the point of catching. That single line is what reconciles the two views - the nested handlers that saw a failure, and the outer ones that saw a value - and without it the system has no place where both facts are visible at once. ## Answering in an interview Say the three positions and what each sees, then name the operational consequence: error dashboards built outside the catching handler are reporting handled-failure-free operation by construction. Interviewers ask this because it separates candidates who picture the chain as a list of steps from those who picture it as nested frames with two different unwind paths.
- A measuring handler records a duration after its inward call. Why does it report nothing for failed calls?Because a failure unwinding past it skips the code written after the inward call; that handler never resumes normally. The recording has to be attached to both the value path and the failure path, so that failed calls are measured too, ideally labelled as failures.
- Where should the catching handler sit relative to a retrying handler?Outside it, if you want retries to happen at all. Nested inside the retrying handler, it converts every attempt into a success, so the retrying handler sees nothing to retry. Outside, it sees the single final failure once the attempts are exhausted.
- What is the difference between catching a failure and translating it into a different one?Catching ends the failure unwind and hands a value outward; translating keeps the unwind a failure under a new name. Outer handlers still take their failure paths after a translation, but they attribute the cause to the new name and can no longer match on the original.
A relay passing a parcel back out: once one runner swaps a damaged parcel for a spare, everyone further along receives an intact parcel and has no reason to report damage.
saying these in an interview costs you the question
- Says the outer handlers still see the original failure
- Believes a handler's after-work runs on the failure path automatically
- Assumes catching a failure also shortens the request's measured duration
- Thinks a failure unwinds in the same order the handlers were entered
- Treats a fabricated fallback as equivalent to a real result