skip to content

When several registered route templates all match one request path, how does a router decide which handler runs?

level: middleimportance: must knowfreq 66%

answer

  1. two rules, not one
  2. specificity or declaration order
  3. literal beats placeholder beats catch-all
  4. ties may be silent
  5. specific before general is always safe

basics

~10 s

Two rules are in use. Most-specific-wins ranks templates structurally, so a literal segment beats a placeholder and a placeholder beats a catch-all whatever the order. First-match-wins simply takes the earliest registration that matches.

solid answer

~40 s

It depends on which precedence model the router implements, and that is the first thing to establish. A **specificity-ranked** router scores each template — literal segment, then constrained placeholder, then plain placeholder, then catch-all, usually compared left to right — and the highest rank wins regardless of where it was declared. A **first-match** router scans registrations in order and stops at the first template that matches, so moving a line changes behaviour. Ties between genuinely indistinguishable templates are resolved by a documented tie-break, by registration order, or by a startup ambiguity error — only some routers detect them at all. Declaring specific routes before general ones is safe under both models, which is why it is worth making a house rule.

go deeper

for a junior

Recall that overlapping routes exist and that the router picks one of them. Learn to name the model your own router uses before reasoning about which handler will run.

for a middle

Explain both models and the usual specificity order, and say what changes when a declaration moves. Be able to describe how a ties case is resolved.

for a senior

Demonstrate you design against precedence rather than discovering it: constrained placeholders, scoped catch-alls, a test that asserts the winning route for each overlapping pair.

for a principal

Own the policy. Decide how overlapping namespaces are allowed to grow across teams, whether route tables are exported for review, and what the cost is of a router change that swaps the precedence model.

## Two precedence models, not one When more than one registered template matches the same request path, the router has to choose. Two rules are in wide use, and a framework picks one of them (sometimes a blend): - **Most-specific-wins.** Every template is ranked by how narrowly it describes a path, and the highest rank wins no matter where it was declared. Declaration order is irrelevant, or is used only to break exact ties. - **First-match-wins.** Templates are tried in registration order and the first one that matches ends the search. Specificity plays no part; moving a line moves behaviour. | | Most-specific-wins | First-match-wins | |---|---|---| | Deciding factor | structural rank of the template | position in the registration sequence | | Effect of moving a declaration | none | can change which handler runs | | Typical implementation | segment tree built at startup | ordered list scanned per request | | Usual failure | a rank rule you did not expect | a general route registered too early | Neither is wrong; they fail differently. The first question to ask about an unfamiliar router is therefore which of the two it implements, because that decides whether *where* a route is declared is part of its meaning. ## How specificity is usually ranked Rankings differ in detail, but most specificity-based routers agree on the broad order, compared segment by segment from the left: 1. an exact literal segment; 2. a placeholder carrying a constraint (a pattern or type rule the segment must satisfy); 3. a plain single-segment placeholder; 4. a catch-all that consumes the remainder of the path. Comparison usually stops at the first position where two templates disagree, so `/orders/search` beats `/orders/{id}` on the second segment and the rest of the template is never consulted. A template with more literal segments generally outranks one with fewer, and a catch-all is the last resort by construction. ## Ties and ambiguity Two templates can be genuinely indistinguishable — `/a/{x}/c` and `/a/b/{y}` both match `/a/b/c`, and neither is more literal at the *same* position. Routers respond in one of three ways: - refuse the pair at startup with an ambiguity error; - apply a documented left-to-right tie-break, so one wins deterministically; - fall back to registration order. Only some routers detect ambiguity at all, so silence at startup is not evidence that a route table is unambiguous. ## Why this is a real bug source Precedence bugs are quiet. The wrong handler runs, returns a plausible response, and nothing logs an error — so the symptom is a feature that *seems* not to be deployed. Common shapes: - a catch-all or prefix route declared early in a first-match router, absorbing everything registered later; - a placeholder route that captures a literal keyword, so `/orders/search` is served as an order whose id is the text `search`; - a route added to a different registration site than the one you are reading, landing at a different position in the same table; - an authorisation-carrying specific route shadowed by a broader one that does not carry it — the same bug, with a security consequence. ## Habits that are safe under both models - **Declare specific before general.** It is required by one model and harmless under the other, so it costs nothing to make it a house rule. - **Constrain placeholders that sit beside literals**, so `/orders/{id}` with a digits-only rule cannot swallow `/orders/search` even if the ranking surprises you. - **Keep catch-alls out of shared namespaces**, or scope them under a prefix that owns nothing else. - **Assert the winner in a test**: for each overlapping pair, a test that names the route expected to handle a representative path. This is the only guard that survives a refactor moving registrations around. - **Print the effective route table** in a startup log or an admin surface, so the order and rank the router actually built are observable rather than inferred from source layout. ## What to say in an interview State that the answer depends on the model, name both, give the usual specificity order, and say which habits are safe regardless. A candidate who asserts one universal rule — usually *first registered wins* — is describing one family of routers as if it were all of them, and that is exactly the assumption that produces the shadowing bug.

  • How is a tie between two equally specific templates usually broken?
    Three ways: the router refuses the pair at startup as ambiguous, it applies a documented left-to-right tie-break so one wins deterministically, or it falls back to registration order. Since many routers detect nothing, a quiet startup is not evidence that a table is unambiguous — a test asserting the expected winner is.
  • Why is a placeholder route that shadows a literal keyword route so easy to miss?
    Because nothing fails. The keyword is captured as an ordinary identifier, the handler runs, and the response looks plausible — often a not-found from a lookup rather than a routing error. The symptom reads as a missing feature, so people search the deployment before they search the route table.
  • What does exposing the effective route table buy you?
    It turns precedence from something inferred out of source layout into something observable: template, methods, and the rank or position the router assigned. It makes shadowing visible in review as a diff, and it answers the question in a deployed environment without a rebuild.

saying these in an interview costs you the question

  • Says the first registered route always wins, in every router
  • Claims declaration order can never matter anywhere
  • Assumes a longer template always beats a shorter one
  • Registers a catch-all early and expects specific routes to win
  • Believes routers reject all overlapping templates at startup