skip to content

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

level: middleimportance: should knowfreq 55%

answer

  1. two failures, not one
  2. path first, method second
  3. empty set versus wrong method
  4. the matched set names the methods
  5. a joint key throws that away

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.

solid answer

~40 s

Routing is two-dimensional, so a router evaluates it in two phases. Phase one collects **every** route whose template matches the path, ignoring the method: an empty set means the path does not exist, which is a 404. Phase two looks inside that set for a route registered for the request's method; if none is there, the path exists but the method does not, which is a 405 — and the phase-one set is exactly the list of accepted methods the response should report. A router that keys its table jointly on method and path cannot make that distinction: every miss looks identical, so wrong-method calls get reported as missing paths and the accepted-method list was never built.

go deeper

for a junior

Recall the distinction: a path that does not exist is 404, and a path that exists but rejects the method is 405. Notice which one your own service returns for a wrong-method call.

for a middle

Explain the two phases and why their order is forced: without the path-matched set there is no way to tell the two failures apart or to name the accepted methods.

for a senior

Connect it to operations. A service that collapses both into a missing-path answer sends every integrator and every log reader chasing the wrong cause, and a catch-all quietly removes the distinction entirely.

for a principal

Treat the routing failure vocabulary as part of the platform contract: consistent statuses across services make client retry and alerting rules possible, inconsistent ones make every integration bespoke.

## Two different failures wearing similar clothes A request can fail routing for two unrelated reasons: **no route describes that path at all**, or **routes describe the path but none accepts that method**. HTTP gives each its own status — 404 for a path that is not there, 405 for a path that is there but does not take this method — and a 405 is expected to tell the caller which methods the path does accept. A router can only tell those apart if it evaluates the two dimensions in order rather than at once. ## Phase one: match the path The router first collects **every** registered route whose template matches the request path, ignoring the method entirely. The result is a set: - **empty** — no template describes this path. The request falls through to the framework's not-found handling, and the honest status is 404. - **non-empty** — the path exists in the route table, and the request is still in play. ## Phase two: select by method Within the non-empty set, the router looks for a route registered for the request's method. 1. If exactly one matches, that handler runs. 2. If none matches, the path exists but the method does not: the answer is 405, and the set from phase one is precisely the list of methods to report back. 3. If several match, the precedence rules for overlapping templates decide, as they would for any other overlap. The key point is that step 2 is only *possible* because phase one kept the path-matched set. Discard it and the router has nothing left but the bare fact that nothing matched. ## Why a combined key loses the distinction Some routers key their table jointly — the lookup value is `(method, path)` — and some key it by path, with a per-path table of methods hanging off it. | | Joint `(method, path)` key | Path first, then method | |---|---|---| | Lookup miss means | nothing matched, reason unknown | path missing *or* method rejected, distinguishable | | Natural failure status | 404 for both cases | 404 or 405, correctly | | Can report the accepted methods | no, the set was never built | yes, it is the phase-one set | | Extra cost | none | one small per-path structure | The joint key is simpler and slightly faster, and it is why some services answer 404 to a wrong-method call. That is not fatal, but it is lossy: the caller cannot tell a typo in the path from a typo in the method, and neither can the engineer reading the access log. ## What else the phase-one set is good for Keeping the path-matched set pays for itself beyond the status choice: - it is the list of accepted methods a router needs in order to answer a request that asks what a path supports, without a handler being written for it; - it lets a router serve a header-only request from the route registered for a body-returning read, when it is configured to do so; - it feeds generated documentation and route-table dumps with a per-path method list; - it makes the *why* observable in tracing: a span or log line can record `path matched, method rejected` instead of a shrug. ## Edge cases worth knowing - **A custom not-found handler does not change the diagnosis.** Routing still failed; the handler is simply what runs afterwards. If it forgets to set a status, a genuine miss is reported as a success, which is far worse than the 404 it replaced. - **A catch-all changes phase one.** Once a wildcard matches everything under a prefix, the path-matched set is never empty there, so nothing below it can ever produce a 404 from the router — the catch-all owns that decision now. - **Method matching is exact.** Methods are case-sensitive tokens, so a lowercased method is a different token, not a sloppy spelling of a known one. - **A path-matched-but-rejected request is still a client error**, not a server error: the route table is behaving as configured. ## How to answer this in an interview Describe the two phases, say what each empty or non-empty outcome means, and note that reporting the accepted methods is free once phase one is kept and impossible once it is not. That framing shows you understand the router as a two-dimensional table rather than as a dictionary of strings.

  • What else does keeping the path-matched set let a router do?
    It can answer a request that asks which methods a path supports without any handler being written, serve a header-only read from the route registered for the full read where configured, feed generated documentation with a per-path method list, and record `path matched, method rejected` in a trace instead of an undifferentiated miss.
  • How does registering a catch-all change this decision?
    It removes it. Once a wildcard matches everything under a prefix, the phase-one set is never empty there, so the router can no longer report a genuine path miss in that subtree — the catch-all handler owns the status choice, and if it does not set one deliberately, a miss is reported as a success.
  • Does a custom not-found handler change what actually happened?
    No. Routing still failed to match; the handler is only what runs afterwards. The risk is that it renders a friendly page and forgets to set the status, turning a clear failure into a successful-looking response that clients, caches and monitoring all read as fine.

saying these in an interview costs you the question

  • Reports a path that exists but rejects the method as missing
  • Thinks the method is matched as part of the path comparison
  • Assumes a wrong-method response needs no list of what is accepted
  • Believes a wrong-method call is a server-side failure
  • Lets a custom not-found handler answer without setting a status