A client sends `Accept: text/html, text/*, */*` in an HTTP request. Explain what the wildcards `text/*` and `*/*` mean and how a server decides which media type to serve.
answer
- three tiers: exact > type/* > */*
- most specific matching range governs
- params make a range more specific
- server breaks remaining ties
- never echo */* in Content-Type
basics
~20 sThey are media ranges of decreasing specificity: an exact type, any subtype of text, then anything. A server matches its available representations against them and the most specific matching range wins, so an HTML representation is preferred here.
solid answer
~50 sEach comma-separated entry in `Accept` is a **media range**, and they come in three specificity tiers: `type/subtype` (exact), `type/*` (any subtype of a type), and `*/*` (anything). A concrete representation matches a range if the range is exact-equal, or is a wildcard that covers it. When several ranges match a candidate representation, HTTP says the **most specific** range applies. So for `Accept: text/html, text/*, */*`, an HTML representation matches all three but is governed by `text/html`; a PNG matches only `*/*`. With no weights in play, the server should prefer HTML. Specificity ordering is: exact type/subtype with parameters > exact type/subtype > `type/*` > `*/*`. In practice a server enumerates what it can produce, filters to those matched by some range, and picks the one governed by the most specific range — breaking ties with its own preference order. It then reports the actual choice in `Content-Type`.
code
http · 9 linesGET /users/42 HTTP/1.1
Host: example.com
Accept: text/html, text/*, */*
HTTP/1.1 200 OK
Content-Type: text/html;charset=utf-8
Vary: Accept
<!doctype html><title>Ada</title>go deeper
Name the three tiers and say that the most specific match wins; give the HTML-vs-PNG example.
Add the full specificity ladder including parameters, and explain that header order is not priority and the server breaks ties.
Talk about implementation reality: parse ranges rather than substring-match, handle */* (the majority of API traffic), keep the choice deterministic for cache keys, and lean on the framework's negotiator.
Weigh whether per-URL negotiation is worth its cache-cardinality and debugging cost versus distinct URLs per representation; reserve range matching for cases like image format fallback where it clearly pays.
## Media ranges An `Accept` header is a list of **media ranges**, not a list of media types. A range is a pattern that a concrete media type may or may not match: | Range | Matches | |---|---| | `text/html` | exactly `text/html` | | `text/html;level=1` | `text/html` carrying the parameter `level=1` | | `text/*` | `text/html`, `text/plain`, `text/csv`, … | | `*/*` | every media type | So `Accept: text/html, text/*, */*` is not three alternatives of equal standing; it is a nested set — HTML specifically, any text, then anything at all. ## The matching rule: most specific wins RFC 9110 states that media ranges can be overridden by more specific ranges, and that a representation is governed by **the most specific range that matches it**. The specificity ladder, from most to least specific: 1. `type/subtype` with media-type parameters (`text/html;level=1`) 2. `type/subtype` 3. `type/*` 4. `*/*` That rule exists mostly so a client can say "anything, but especially X" — and, when weights are used, so it can say "anything except X" by pinning a specific range low. Even without weights, the ladder gives the server a defensible preference order. ## The server's algorithm A typical implementation: 1. Build the list of representations it can actually produce for this resource, e.g. `[application/json, text/html]`. 2. For each candidate, find the most specific matching range in `Accept`. Candidates matched by no range are eliminated. 3. Sort survivors by that range's specificity (and by weight, where weights are present). 4. Break remaining ties with the **server's own** preference order — HTTP explicitly allows the server to prefer, and a well-behaved API usually prefers JSON. 5. Serve it, and set `Content-Type` to the exact concrete type chosen (never a wildcard — `Content-Type: */*` is meaningless and invalid). For the example header with candidates `[application/json, text/html]`: JSON is governed by `*/*`, HTML by `text/html`. HTML is more specifically requested and wins. ## Parameters Media types can carry parameters: `text/html;charset=utf-8`, `application/vnd.example+json;version=2`. Matching against parameters is exact per-parameter, and `charset` is a frequent trap: `Accept: text/html;charset=utf-8` is a *more specific* range that a plain `text/html` representation does not match. Servers vary in strictness here, which is one reason clients rarely put parameters in `Accept` beyond weights. ## What browsers actually send A modern browser navigation looks roughly like: ``` Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8 ``` The trailing `*/*` is what makes browsers usable: it guarantees the server can always serve *something*. Image requests send a much narrower list (`image/avif,image/webp,image/apng,*/*`), which is exactly how a single `/logo` URL can return AVIF to Chrome and PNG to an old client. API clients are usually far blunter — many libraries send `Accept: */*` by default, which means the server's own default wins. Do not design an API on the assumption that clients send a thoughtful Accept list. ## Common implementation pitfalls - **Substring matching.** Checking `accept.contains("json")` matches `application/json` but also `text/json-fake`, and misses `*/*`. Parse the list into ranges instead. - **First-listed wins.** Order in the header is not significance; specificity (and weight) is. Treating position as priority quietly breaks browser requests, where `text/html` happens to be first only by convention. - **Ignoring wildcards entirely.** A server that only matches exact strings will fail every `*/*` request — which is most programmatic clients — and may wrongly refuse them. - **Echoing the wildcard.** `Content-Type` must name the concrete type actually sent. ## Rule of thumb Match by range, prefer the most specific match, let the server break ties, and always report the real type back. If you find yourself writing bespoke parsing, use the framework's negotiation support — nearly every HTTP stack ships one.
- Two available representations are matched by ranges of identical specificity. Who decides?The server. HTTP allows the origin server to apply its own preference order when the client expresses no distinguishing preference. Most APIs prefer JSON; content sites prefer HTML. The decision should be deterministic so caching and debugging stay sane.
- Why is `Accept: */*` so common in API traffic, and what does it imply for your API design?Most HTTP client libraries and curl send `*/*` by default because they have no opinion. It means your server's default representation is what almost everyone gets, so the default must be the right one — and it means you cannot rely on Accept to route versions unless clients are explicitly built to set it.
- Does the order of entries in an Accept header establish priority?No. Priority comes from specificity and from weight parameters, not position. `Accept: */*, text/html` and `Accept: text/html, */*` mean the same thing. Implementations that sort by position produce subtly wrong choices for browser requests.
Like a job filter reading "backend roles, any engineering role, any role at all" — the most specific rule that describes a given job is the one that decides how much you want it.
saying these in an interview costs you the question
- Claiming the first media range listed always wins.
- Substring-matching the raw Accept string instead of parsing media ranges.
- Treating `*/*` as 'no preference, so return 406' rather than 'anything is fine'.
- Returning `Content-Type: */*` or a wildcard in the response.
- Believing `text/*` matches `application/json` because both are text-ish formats.