When two http.ServeMux patterns match the same request, how does Go decide which one wins?
answer
- not first match, not longest string
- compare the sets of requests matched
- strict subset wins
- no subset means the pair is rejected
- the rejection happens at registration
basics
~20 sThe more specific pattern wins: the one whose set of matching requests is a strict subset of the other's. Registration order is irrelevant. If neither pattern is a subset of the other they conflict, and registering the second one panics.
solid answer
~50 sGo's `ServeMux` resolves overlaps by **specificity**, not by order. Pattern P1 beats P2 when P1 matches a strict subset of the requests P2 matches — P2 matches everything P1 does, and more. So `"GET /items/new"` beats `"GET /items/{id}"` for the path `/items/new`, and `"/images/thumbnails/"` beats `"/images/"` for anything under the thumbnails subtree, no matter which was registered first. When neither pattern's request set contains the other's, the patterns **conflict**: `"GET /"` and `"/index.html"` both match a GET for `/index.html`, but each also matches requests the other does not. Registering both makes `mux.Handle` or `mux.HandleFunc` panic, so a conflict is a startup crash you find in a test, not a mysterious 404 in production. One backwards-compatibility exception: if two otherwise-conflicting patterns differ in that one carries a host and the other does not, the host pattern wins instead of panicking.
code
go · 7 linesmux := http.NewServeMux()
mux.HandleFunc("GET /items/{id}", showItem)
mux.HandleFunc("GET /items/new", newItemForm) // more specific for this one path
mux.HandleFunc("GET /items/{id}/tags", itemTags)
// GET /items/new -> newItemForm, whatever order these lines appear in
// GET /items/42 -> showItemgo deeper
Remember the headline: Go's standard mux does not take the first matching route. The narrower pattern serves the request no matter when it was registered.
Explain specificity as a strict-subset relation between the request sets two patterns match, and name the consequence when neither is a subset: the registration call panics.
Demonstrate that a legal overlap can still change behaviour, so adding a literal route beside a wildcard route needs a test pinning which pattern serves which path, and that a conflicting pattern is a boot failure worth catching in CI.
Own the consequence for a codebase: order-independent routing removes a class of merge hazard, but a panic on a conflicting pattern is an availability event, so make building the real mux part of the test suite everyone inherits.
## The rule in one sentence If two or more registered patterns match a request, the **most specific** one takes precedence; if no pattern is most specific, the patterns conflict and registering them panics. ## What "more specific" actually means Specificity is defined on **request sets**, not on string length and not on how many wildcards a pattern has. Think of each pattern as the set of all requests it matches. Pattern P1 is more specific than P2 when P1's set is a strict subset of P2's: every request P1 matches is also matched by P2, and P2 matches at least one request P1 does not. Worked through: - `"/items/new"` versus `"/items/{id}"`. The literal matches exactly one path. The wildcard matches that path *and* `/items/42`, `/items/anything`. Strict subset, so the literal is more specific and serves `/items/new`. The wildcard still serves every other id. - `"/images/thumbnails/"` versus `"/images/"`. Everything under the thumbnails subtree is also under the images subtree, but not the reverse. The thumbnails pattern is more specific. - `"GET /items/{id}"` versus `"/items/{id}"`. The method-constrained pattern matches a subset (GET and HEAD only), so it wins for GET requests while the unconstrained pattern keeps POST, PUT and the rest. Notice what is **not** the rule. It is not longest-string-wins: `"GET /"` is shorter than `"/index.html"` and neither wins. It is not literals-always-beat-wildcards as a standalone axiom — that falls out of the subset rule when the literal is otherwise identical, but not when the two patterns disagree in different segments. And it is emphatically not first-match: several widely used routers walk their route list in registration order and take the first hit, so an engineer arriving from one of those will reach for reordering as a fix and find it does nothing. ## Conflicts When neither pattern's request set contains the other, there is no defensible winner, so the mux refuses to choose. The documentation's own example is `"GET /"` and `"/index.html"`: both match a GET for `/index.html`; the first also matches every other GET; the second also matches a POST for `/index.html`. Neither set is a subset of the other. Registering both panics. A subtler conflicting pair is `"/items/{id}/tags"` and `"/items/a/{x}"`: both match `/items/a/tags`, but the first also matches `/items/b/tags` and the second also matches `/items/a/colours`. Wildcards that cross each other in different positions are the usual source of accidental conflicts. The panic happens **at registration**, inside `Handle` or `HandleFunc`, with a message naming both patterns. That is a deliberate design choice: a routing ambiguity becomes a process that will not start, which a smoke test or a `TestMain` that builds the real mux catches immediately. It also means a conflicting route added by a merge takes the service down at boot rather than silently stealing traffic — worth saying in an interview, because it is a genuine availability consideration and the argument for building the production mux inside a unit test. ## The one exception For backwards compatibility, if two patterns would otherwise conflict and exactly one of them has a host, the pattern with the host takes precedence rather than the pair being rejected. This preserves the older behaviour where a host-qualified registration was understood to be narrower. It is the only place where the subset rule is overridden, and it is worth naming because it is the kind of detail a follow-up question probes. ## Why order-independence is a feature With first-match routing, the correctness of your routing table depends on the sequence of lines in a file. Two developers adding routes in the same release can reorder each other's matches through a merge, and a broad catch-all placed early makes everything after it unreachable without any error. Go's rule removes that entire class of bug: you can register routes in any order, group them however reads best, and split registration across functions or packages. The price is that overlaps are resolved by a rule you must internalise, and that an ambiguous pair is fatal rather than arbitrary. ## What to do about it in practice Because an overlap can be legal *and* still change which handler serves an existing path, treat the routing table as behaviour under test. Build the real mux in a test and assert, for a table of representative paths, which pattern each one matches. Add a row whenever you add a route. That converts both failure modes — a silent shadow and a startup panic — into a red test on the pull request that introduced them.
- Does changing the order of the registration calls change which pattern serves a request?No. `ServeMux` compares the patterns themselves, so the outcome is identical however you order, group or split the registrations. That is the main behavioural difference from routers that walk a list and take the first hit, and it is why reordering is never the fix for a route that reaches the wrong handler.
- What happens at startup when two conflicting patterns are registered?`Handle` and `HandleFunc` panic, naming both patterns. The process fails to start rather than serving an arbitrary choice. Build the production mux inside a test so the panic surfaces in CI; otherwise the first evidence is a crash loop on deploy.
- How does a host in the pattern affect precedence?A pattern with a host matches only requests for that host, with the port stripped, so it is normally narrower on its own terms. There is also one backwards-compatibility exception: if two patterns would otherwise conflict and only one carries a host, the host pattern takes precedence instead of the pair being rejected.
It is a dictionary of addresses, not a stack of filters. The mux hands the request to the entry that describes it most narrowly, regardless of what order the entries were written down — and if two entries describe it equally well, it refuses to guess.
saying these in an interview costs you the question
- Says the first registered pattern wins
- Says the longest pattern string always wins
- Thinks a conflict is resolved silently at request time
- Believes reordering registrations can fix a mismatch
- Assumes any overlap between two patterns is illegal