skip to content

In Go's http.ServeMux, what does the pattern "GET /items/{id}" match, and how does the handler read id?

level: juniorimportance: must knowfreq 72%

answer

  1. the method is part of the pattern text
  2. braces mark exactly one path segment
  3. GET also answers HEAD
  4. the wildcard is looked up by its name
  5. the value arrives as an unvalidated string

basics

~10 s

It matches GET and HEAD requests whose path is exactly two segments: the literal items followed by any single segment. The handler reads that segment with r.PathValue("id"), which returns it as a string.

solid answer

~40 s

Since Go 1.22 a `ServeMux` pattern has the shape `[METHOD ][HOST]/[PATH]`, so `"GET /items/{id}"` constrains both the method and the path. The method part matches GET, and a GET pattern also matches HEAD; a pattern with no method matches every method. `{id}` is a wildcard that matches exactly **one** path segment, so `/items/42` matches but `/items/42/reviews` does not and `/items` does not. A wildcard must be a whole segment — `"/items/item-{id}"` is invalid and `mux.HandleFunc` panics on it at registration. Inside the handler, `r.PathValue("id")` returns the matched segment as a string, already unescaped, and returns `""` if no such wildcard matched. You still parse and validate it yourself before using it as a database key. A POST to the same path gets a 405 with an `Allow` header, not a 404.

code

go · 10 lines
go
mux := http.NewServeMux()
mux.HandleFunc("GET /items/{id}", func(w http.ResponseWriter, r *http.Request) {
	id := r.PathValue("id") // always a string, already unescaped
	fmt.Fprintf(w, "item %s", id)
})

// GET  /items/42         -> matches, id == "42"
// HEAD /items/42         -> matches the same pattern
// GET  /items/42/reviews -> no match: {id} covers one segment only
// POST /items/42         -> 405, with an Allow header

go deeper

for a junior

Be ready to write the registration line and read the value back: HandleFunc with a method and a {name} segment, then r.PathValue in the handler. Say out loud that the value arrives as a string you still have to validate.

for a middle

Explain the segment rule precisely: a {name} wildcard covers exactly one segment, a trailing {name...} covers the rest, and an invalid wildcard panics at registration rather than failing at request time.

for a senior

Show that you check the unhappy paths: what a wrong method returns, what an empty PathValue means, and how the value is parsed and bounded before it ever reaches a query or a file path.

for a principal

Frame what changes for a team when the standard library matches methods and path variables: a routing dependency stops being automatic, and the argument for one has to be made on features it actually adds.

## What a ServeMux is `http.ServeMux` is the standard library's HTTP request multiplexer. You register patterns against handlers, and the mux — which is itself an `http.Handler` — compares each incoming request against every registered pattern and dispatches to the winner. `http.NewServeMux()` gives you a fresh one. ## The pattern grammar Since Go 1.22 a pattern looks like: ``` [METHOD ][HOST]/[PATH] ``` All three parts are optional, and `"/"` is a valid pattern. If a method is present it must be followed by whitespace before the path. So `"GET /items/{id}"` is a method (`GET`), no host, and a path with one literal segment and one wildcard segment. **Method rules.** A pattern with no method matches every method. A pattern with `GET` matches GET *and* HEAD, because the standard library treats HEAD as a GET whose body is discarded. Any other method must match exactly. **Host rules.** A pattern with no host matches every host; `"api.example.com/items/{id}"` matches only requests whose Host header names that host, with the port stripped. **Wildcard rules.** `{id}` matches exactly one path segment, ending at the next literal slash. The name must be a valid Go identifier. Wildcards must be complete segments: they have to be preceded by a slash and followed by a slash or the end of the pattern, so `"/items/item-{id}"` is not a valid pattern and registering it panics. The trailing form `{rest...}` matches all remaining segments including the slashes between them, and is only legal at the very end of a pattern. ## Reading the value `r.PathValue(name)` returns the string the named wildcard matched, or the empty string if the request did not match a pattern with that wildcard. Two things surprise newcomers: 1. **It is always a string.** There is no typed binding, no integer constraint on the wildcard, and no validation. If the id is a database key you convert it yourself (`strconv.Atoi`, a UUID parse, a lookup against an allowlist) and return 400 on failure. A route that reads `r.PathValue("id")` and interpolates it straight into a query has done nothing for you that a query parameter would not. 2. **The value is already unescaped.** Pattern paths and request paths are matched segment by segment after unescaping, so a request for `/items/a%2Fb` is one segment whose value is `a/b`, and `r.PathValue("id")` gives you `a/b` rather than the percent-encoded form. There is a matching `r.SetPathValue(name, value)`, which exists mainly so tests and wrappers can synthesise a request as though it had matched a pattern. ## The three outcomes for /items/42 Given only `"GET /items/{id}"` registered: - `GET /items/42` — matches; `PathValue("id")` is `"42"`. - `HEAD /items/42` — matches the same pattern. - `POST /items/42` — the *path* matches a registered pattern but the *method* does not, so the mux answers 405 Method Not Allowed and sets an `Allow` header listing the methods registered for that path. This is a real improvement over hand-rolled routing, where a wrong method usually fell through to 404 and told the client nothing. - `GET /items` or `GET /items/42/reviews` — no registered pattern matches, so 404. ## Why this matters for the shape of a handler Before Go 1.22 the standard mux matched only path prefixes, so nearly every real service either split `r.URL.Path` by hand or pulled in a routing dependency. Method-and-wildcard patterns mean an ordinary CRUD resource — list, fetch by id, update by id — can be registered directly against the standard library, with the method check done by the mux instead of a `switch r.Method` at the top of every handler. That is the single biggest practical change to `net/http` in years, and it is why an interviewer asks the question at all. If you must run on a toolchain older than 1.22, or you need the old prefix semantics back for one deployment, `GODEBUG=httpmuxgo121=1` restores the pre-1.22 matcher.

  • Does that pattern also serve a HEAD request for the same path?
    Yes. A pattern whose method is GET matches both GET and HEAD requests, so you do not register HEAD separately. The server discards the body of a HEAD response for you. Any other method — POST, PUT, DELETE — must be spelled out in its own pattern.
  • Only "GET /items/{id}" is registered and a POST arrives at /items/42. What does the client get?
    405 Method Not Allowed, with an `Allow` header naming the methods registered for that path — not a 404. The mux distinguishes "this path is unknown" from "this path exists but not for your method", which is the response clients and API tests expect.
  • Is "/items/item-{id}" a legal ServeMux pattern?
    No. A wildcard has to be a complete path segment: preceded by a slash and followed by a slash or the end of the pattern. `"/items/item-{id}"` mixes a literal prefix with a wildcard inside one segment, so `Handle` and `HandleFunc` panic at registration. Match the whole segment and strip the prefix in the handler.

saying these in an interview costs you the question

  • Says a {id} wildcard spans several path segments
  • Claims the standard mux still cannot match methods or path variables
  • Expects a wrong method to produce 404 rather than 405
  • Assumes PathValue returns a validated or typed value
  • Writes a wildcard inside a segment, like /items/item-{id}