skip to content

Route Matching Rules

How a router matches a path: static versus dynamic segments, wildcards, specificity, slash and case rules, and why a path miss is 404 but a method miss is 405. Asked because precedence bugs hide.

on this pageshow

questions

5

In a web framework's router, how does a path template with a variable segment differ from a fully static path?

level: juniorimportance: must knowfreq 70%

answer

  1. one pattern, many paths
  2. segments split on the separator
  3. literal compared, placeholder captured
  4. a plain placeholder stops at a slash
  5. path only, captured as text

basics

~20 s

A static path matches one exact sequence of literal segments. A template such as /orders/{id} matches any single segment in that position and captures its text as a named path variable the handler can read.

solid answer

~40 s

A router stores every registered route as a **path template**: segments split on `/`, where each segment is either a literal compared exactly or a placeholder such as `{id}` or `:id`. Matching walks the request path segment by segment — a literal must be equal, a placeholder accepts whatever single segment is present and records it under its name — and succeeds only if template and path end together. A plain placeholder stops at the next `/`, so `/files/{name}` does not match `/files/a/b`; spanning a run of segments needs an explicit catch-all form. Only the path takes part: the query string is not in the template, and a capture arrives as raw text.

go deeper

for a junior

Recall the shape: literal segments match themselves, a placeholder matches exactly one segment and hands you its text. Say out loud which paths a given template does and does not match.

for a middle

Explain the walk: split on the separator, compare position by position, succeed only when both run out together. Be ready to describe catch-all and constrained placeholders and what each changes.

for a senior

Show that a capture is untrusted client input and that empty or unusual segments depend on normalisation policy. Talk about templates you tested rather than assumed.

for a principal

Frame templates as a service's public surface: shape, depth and placeholder discipline decide how routes can evolve, and a constrained placeholder is cheap insurance against future keyword routes.

## What a route template is A **route template** is the pattern a router stores for each registered handler. It is written as a path — a sequence of segments separated by `/` — and each segment is one of two things: - a **literal** (static) segment such as `orders`, which matches only itself, character for character; - a **placeholder** (path variable, path parameter) such as `{id}` or `:id`, which matches whatever single segment sits in that position and remembers its text under the name written inside it. So `/orders` is a fully static template: exactly one request path can ever match it. `/orders/{id}` is a dynamic template: an unbounded set of paths match it, and every match produces one captured value. ## How a match is attempted A typical router does roughly this for each incoming request: 1. Take the **path** of the request target — the part after the host and before any `?`. 2. Normalise it as configured: decode percent-escapes, resolve or reject dot segments, apply whatever slash and case policy the router was told to use. 3. Split the result on `/` and walk the candidate templates segment by segment. 4. A literal must compare equal; a placeholder consumes exactly one segment; the match succeeds only if template and path run out at the same time. 5. On success, hand the handler the captured values keyed by placeholder name. Step 4 carries the rule newcomers most often miss: a plain placeholder is **single-segment**. `/files/{name}` matches `/files/report.pdf` but not `/files/archive/report.pdf`, because the second path has one segment too many. Matching a remaining *run* of segments requires an explicit catch-all form instead. ## The kinds of segment | Segment kind | What it matches | What it captures | |---|---|---| | Literal `orders` | that exact text, and nothing else | nothing | | Placeholder `{id}` | exactly one segment, any text | that segment, by name | | Constrained placeholder | one segment that also satisfies a pattern or type rule | that segment, by name | | Catch-all / wildcard | the remainder of the path, separators included | the remainder, usually by name | The **constrained** placeholder deserves a callout because the constraint takes part in *matching*, not only in validation: if the segment fails the pattern, that template did not match at all and the router is free to try another. That is how a digits-only `/orders/{id}` and a literal `/orders/search` can coexist without the literal route being shadowed. Not every router supports constraints, and those that do differ in how rich the pattern may be. ## What the template does not cover - The **query string** is not part of the template. `/search?q=x` matches the template `/search`; the parameters are read separately inside the handler. - The **fragment** after `#` never reaches the server at all. - The **method** is considered separately, after the path — which is why a path that exists but rejects the method is a different failure from a path that does not exist. - **Scheme, host and port** normally sit outside the template, although some routers allow an extra host constraint beside it. ## Captured values are plain text A capture is a slice of the request path, so it arrives as a string. Turning `42` into a number, a date or an identifier object is a later step with its own failure mode, and the template says nothing about it. Two practical consequences follow: - A captured value is **client-controlled input** exactly like a body field. A path variable that flows into a query, a file path or a redirect target needs the same handling as any other untrusted string. - An empty segment is not the same as an absent one. `/orders//items` contains an empty middle segment, and whether that matches `/orders/{id}/items` with an empty capture or fails outright depends on the router's normalisation policy — worth a test rather than an assumption. ## Why routers precompile templates Templates are usually parsed once at startup into a structure — commonly a tree keyed by segment — rather than re-parsed per request. Matching then costs roughly the length of the path instead of a scan over every registered route, which matters once a service has hundreds of them. The same structure is what lets a router print its whole table for inspection and what makes an overlapping pair of templates detectable before any traffic arrives. ## Where this bites in practice - Registering `/users/{id}` and expecting it to serve `/users` too. It does not: the segment counts differ. An optional tail is normally two templates, or one template with an explicitly optional segment where the router offers that. - Assuming a placeholder will swallow a separator, then finding that identifiers containing `/` produce a path miss instead of a capture. - Writing leading and trailing slashes inconsistently across a codebase and leaning on the router's slash policy to paper over the difference.

  • How can a route match an identifier that itself contains a separator character?
    Not with a plain placeholder, which stops at the separator. Either use a catch-all segment that consumes the remainder of the path and split the value yourself, or require the client to escape the character and be sure the router splits into segments before decoding, otherwise the escape re-appears as a separator. Most designs simply avoid separator-bearing identifiers in the path.
  • What does adding a constraint to a placeholder change about matching?
    The constraint becomes part of the match, not a later validation. A segment that fails the pattern means the template did not match at all, so the router carries on and another template may win. That is how a digits-only identifier route and a literal keyword route on the same position can coexist safely.

saying these in an interview costs you the question

  • Thinks a plain path variable also matches across separators
  • Believes the query string is part of the route template
  • Assumes /users/{id} also serves the bare /users path
  • Expects the router itself to convert a capture to a number
  • Treats a captured path variable as trusted because the route matched
open as a page

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

level: middleimportance: must knowfreq 66%

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.

open as a page

Why must a router collect every route matching the request path before it can choose between 404 and 405?

level: middleimportance: should knowfreq 55%

basics

~20 s

A path miss and a method miss are different failures. If no template matches the path, the answer is 404; if templates match but none takes the request method, it is 405 and the matched set names the accepted methods.

open as a page

How can a router treat a trailing slash or a case difference between the request path and a registered template?

level: middleimportance: should knowfreq 44%

basics

~20 s

Three policies are common: strict matching treats the spellings as different paths and misses, lenient matching sends both to one handler, and canonicalising redirects to the preferred form. Path segments are case-sensitive by specification, so ignoring case is an opt-in.

open as a page

In a router, a newly added route never runs because an older catch-all serves its path — how do you diagnose and prevent that?

level: seniorimportance: should knowfreq 48%

basics

~20 s

Replay the exact path, then read the router's effective route table and which template it selects — the handler you are debugging never ran. Fix it by narrowing the catch-all, then pin the winner with a test.

open as a page