skip to content

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%

answer

  1. two axes: nesting and registration
  2. outer scope wraps inner by construction
  3. registration order sorts peers only
  4. outbound unwinds widest scope last
  5. move it up or branch the pipeline

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.

solid answer

~40 s

The chain is composed by nesting: the application-wide hooks wrap the group's, the group's wrap the route's, and the handler sits innermost. A route-scoped hook is only reachable by delegating through the hooks above it, so it cannot run before them no matter when it was registered — registration order decides only the relative order of hooks attached at the *same* scope. The practical consequence is that you cannot interleave scopes by reordering declarations. If a check must run before something global, it has to move to the global scope, usually guarded by a condition or by branching the pipeline. Frameworks differ here: some order strictly by nesting, others expose an explicit priority or phase that can reorder across scopes, so confirm which model you are in before relying on it.

go deeper

for a junior

Remember the shape: wider scopes are outside narrower ones, the handler is innermost, and a route's own hooks are reached only after everything above them has delegated.

for a middle

Separate the two axes cleanly. Nesting decides containment across scopes; registration order decides sequence among hooks attached at the same scope, and only there.

for a senior

Recognise the fix rather than fighting the model: move the hook to a wider scope with a guard, or branch the pipeline, and say why the response-rewriting order follows from the same nesting.

for a principal

Make cross-scope order a design constraint. Decide which stages are reserved platform-wide so teams cannot accidentally depend on being outside something that must stay outermost.

Two independent things decide the order hooks run in. **Scope nesting** decides which chain contains which, and **registration order** decides the sequence among peers. Candidates who have only ever added hooks in one place tend to collapse the two and expect declaration order to be the whole story. ## Why nesting dominates A chain is built by composition: each element receives the remainder of the pipeline as something it can call. The framework assembles that from the outside in — application scope, then any enclosing groups, then the route's own hooks, then the handler. A route-scoped hook is therefore *inside* the global one by construction: the only way execution arrives at it is for the global hook to delegate. Nothing about declaring it earlier in the source can change that, because the two are not peers being sorted; one is the content of the other. Registration order still matters, but its jurisdiction is narrow: - Among hooks attached at the **same** scope, the framework sorts them by registration order (or by an explicit index or priority if it offers one). - Across **different** scopes, the containment relation decides, and registration order is not consulted. | You want | Works? | Why | |---|---|---| | A route hook to run before a global one | No, under pure nesting | The route chain is reached only by delegating through the global one | | Two global hooks in a chosen order | Yes | Same scope, so registration order applies | | A group hook to run before its parent group's | No | The parent encloses the child by construction | | A global hook to apply to only some routes | Yes | Register it globally and guard it, or branch the pipeline | ## The outbound direction Because the composition is an onion, the after-phases unwind in reverse: the innermost route-scoped hook finishes first on the way out, and the outermost global hook finishes last. So a global hook is both the first to see the request and the last to see the response, and a route-scoped hook is the opposite. That matters whenever two hooks at different scopes both want to modify a response — the wider one always gets the final word, which is why a response-rewriting hook placed globally can overwrite what a route-scoped hook carefully set. ## When you genuinely need to cut across scopes If a check must run before something that is currently global, nesting will not let you push it down; you have to change where it lives. The usual options, in increasing cost: 1. **Move it up.** Register it at the global scope, ordered before the hook it must precede, and guard it so it does nothing for requests it does not care about. Cheap, but the scope becomes invisible from the route tree. 2. **Branch the pipeline.** Split the routes into two branches at registration time, each with its own chain in its own order. Explicit and readable, at the cost of stating the common part twice or factoring it into something shared. 3. **Use an explicit priority, if the framework has one.** Some frameworks let a hook declare a numeric order or a named phase that is honoured across scopes, or run certain categories in a fixed stage before anything user-registered. This is the case where the generic mental model has to give way to the specific documentation. ## Diagnosing order problems When the observed order surprises you, ask three questions in this sequence. *Are these two hooks at the same scope?* If not, nesting explains it and no amount of reordering declarations will help. *Does the framework offer a priority that overrides nesting?* If it does, some other hook may have claimed a stage you assumed you owned. *Did anything short-circuit?* A hook that responds without delegating removes everything inner from the request entirely, which looks like a hook that ran "out of order" when in fact it never ran. The habit worth building is to treat cross-scope order as a structural property you design at registration, not a runtime detail you tune afterwards. If the correct order cannot be read off the way scopes are nested, the next reader will get it wrong.

  • A route-scoped hook must run before a global one. What are the options?
    Move it to the global scope and order it ahead, guarding it so it does nothing for uninterested requests; or branch the pipeline so the routes in question get their own chain in the order you want. Under pure nesting, reordering declarations achieves nothing.
  • Which scope has the last word on the outgoing response?
    The widest one still in the chain. After-phases unwind from the inside out, so a route-scoped hook finishes before its group's, which finishes before the application's. A global response rewriter can therefore overwrite what a narrower hook set.
  • Does registration order ever matter across scopes?
    Not under pure nesting, where containment decides. It can matter in frameworks that expose an explicit priority or a fixed set of phases honoured across scopes; there, the declared order or phase wins and nesting only breaks ties. Confirm which model applies before designing around it.

Envelopes inside envelopes: the outer one is opened first and sealed last, and no amount of addressing the inner envelope earlier gets it opened before the one containing it.

saying these in an interview costs you the question

  • Thinks declaring a route hook earlier makes it run before a global one
  • Believes registration order alone determines execution order everywhere
  • Expects the narrowest hook to have the final word on the response
  • Assumes every framework orders purely by nesting with no priority mechanism
  • Reorders declarations for an hour instead of checking whether scopes differ