skip to content

What does a quality value of zero mean in HTTP request headers such as 'Accept-Encoding: gzip, identity;q=0', and how would you design server-side selection when Accept, Accept-Language and Accept-Encoding weights disagree about which stored representation is best?

level: seniorimportance: should knowfreq 24%

answer

  1. q=0 = unacceptable, absolute, not outvoted
  2. identity;q=0 forbids uncompressed; *;q=0 = listed codings only
  3. 406 or serve default — spec allows both
  4. axes independent; combination is policy not spec
  5. Vary lists every negotiated header; cardinality explodes

basics

~20 s

q=0 means explicitly unacceptable — 'identity;q=0' forbids an uncompressed body. Each Accept-* header is an independent axis; the server scores each representation per axis, combines with fixed axis priority or multiplication plus its own quality factors, and must emit Vary listing every header it negotiated on.

solid answer

~50 s

`q=0` is a refusal, not a low preference: `identity;q=0` means "do not send this uncompressed", and `*;q=0` refuses anything not explicitly listed with a positive weight. If nothing the server can produce is acceptable, HTTP permits either a `406` or serving the default anyway — the spec deliberately allows disregarding preferences, because a slightly-wrong body usually beats an error.\n\nAcross axes, `Accept`, `Accept-Language` and `Accept-Encoding` are **independent dimensions**, and HTTP does not specify how to combine them. Practical approach: score each stored variant per axis (client q times your own server quality factor), drop any variant scoring 0 on any axis, then combine by **lexicographic axis priority** — media type first, then language, then encoding — because encoding is a transfer detail that should never decide *which document* the user reads. Multiplying the per-axis scores also works but lets a compression preference outvote a language one. Then emit `Vary: Accept, Accept-Language, Accept-Encoding` and accept the cache-cardinality cost.

code

http · 1 line
http
GET /report HTTP/1.1\nHost: example.com\nAccept: application/json\nAccept-Language: de, en;q=0.7\nAccept-Encoding: gzip, identity;q=0\n\nHTTP/1.1 200 OK\nContent-Type: application/json\nContent-Language: de\nContent-Encoding: gzip\nVary: Accept, Accept-Language, Accept-Encoding

go deeper

for a junior

Know only that q=0 means the client refuses that option, and that identity;q=0 forbids an uncompressed body.

for a middle

Add that the server may either 406 or serve its default, and that each Accept-* header is a separate independent axis.

for a senior

Give a concrete selection algorithm — score, eliminate zeros, priority-ordered combination, deterministic tie-break — and connect it to Vary and cache correctness.

for a principal

Argue the policy: which axes are worth negotiating at all, distinct URLs versus Vary for language, normalisation at the edge, and the cache-cardinality cost you are willing to pay.

## q=0: refusal, not weak preference\n\nA quality value of zero states the option is **not acceptable**. Two idioms matter:\n\n- `Accept-Encoding: gzip, identity;q=0` — the client wants gzip and refuses an uncompressed body. Rare, but real for constrained links and for proxies enforcing compression.\n- `Accept-Encoding: gzip, *;q=0` — gzip only; every other coding, including `identity`, is refused.\n\nThe wildcard interacts with defaults: in `Accept-Encoding`, `identity` is acceptable by default unless explicitly refused, so `*;q=0` is the way to forbid it. An empty `Accept-Encoding` field value means "no coding at all is preferred", while an absent header means "anything, but the server should be conservative".\n\nWhen nothing is acceptable, the server's options are: return `406 Not Acceptable` (or `415` for a rejected request body — a different mechanism), or **serve its default anyway**. HTTP explicitly allows the latter, on the grounds that user agents can often cope with an unrequested type but cannot cope with an error page. Public APIs usually pick strictness — a `406` with a machine-readable list of supported types — while document servers pick leniency.\n\n## Multiple axes, no specified algorithm\n\nA request carries several negotiation dimensions at once:\n\n```\nAccept: application/json, text/html;q=0.9\nAccept-Language: de, en;q=0.7\nAccept-Encoding: br, gzip;q=0.8\n```\n\nThe server may hold variants that are not best on every axis at once: a German HTML page, an English JSON document, a Brotli-only asset. HTTP defines what each header *means* but not how to reconcile them. That is a deliberate gap — the answer is policy, and the interviewer is testing whether you know it is policy.\n\n### A workable algorithm\n\n1. **Enumerate variants** the origin can actually produce, each tagged with its media type, language, and available codings.\n2. **Score per axis**: client q for that axis's matching range (most specific match wins), multiplied by your own server quality factor for the variant on that axis.\n3. **Eliminate**: any variant scoring 0 on any axis is out — a refusal is absolute, not outvoted by a strong score elsewhere.\n4. **Combine.** Two schools:\n - *Multiplicative*: score = type × language × encoding. Simple and what Apache's `mod_negotiation` approximates. Risk: a 0.9 vs 1.0 encoding preference can flip the chosen *language*, which no user would want.\n - *Lexicographic by axis priority*: sort by media-type score, then language, then encoding. This encodes the real hierarchy — encoding is a transfer-level concern and must never change which document you get, and content-coding is orthogonal enough that it should be applied *after* the representation is chosen.\n5. **Tie-break** deterministically (fixed preference list, then variant identifier), so the same request always yields the same variant. Non-deterministic selection poisons caches and makes bug reports irreproducible.\n\n### Encoding is special\n\nTreat `Accept-Encoding` as a separate, later step: choose the representation from type and language, then choose a coding for it. A representation with no acceptable coding does not become a different document; it becomes the same document sent as `identity`, or a `406` if `identity` was refused.\n\n## Cache consequences\n\nEvery axis you negotiate on must appear in `Vary` on the response, e.g. `Vary: Accept, Accept-Language, Accept-Encoding`. Shared caches key entries on the URL plus the listed request headers **verbatim**, and real-world header strings are wildly varied — dozens of browser `Accept` variants, hundreds of `Accept-Language` strings. `Vary: Accept-Language` alone can shatter one hot object into hundreds of near-identical cache entries, each with its own miss rate. Mitigations:\n\n- Negotiate on the **fewest** axes you can; drop `Vary: Accept` entirely for endpoints that only ever return JSON.\n- Normalise at the edge: a CDN worker collapses the incoming header to one of a small set of canonical values before it reaches the origin, so the cache key has low cardinality.\n- For language especially, prefer **distinct URLs** (`/de/page`, `/en/page`) with negotiation only on the entry point, redirecting once and returning `Content-Language` plus `Content-Location` so the specific variant is separately addressable, linkable and cacheable.\n- `Vary: *` means the response is uncacheable by shared caches — occasionally correct, usually a mistake.\n\n## What good answers sound like\n\nName q=0 as refusal, state that cross-axis combination is unspecified policy, propose axis priority with elimination on zeros, and close on `Vary` cardinality — because the caching bill, not the selection logic, is what actually hurts in production.

  • A client sends 'Accept-Encoding: gzip, identity;q=0' and your origin cannot gzip. What do you send?
    Either a 406 Not Acceptable, or the uncompressed body anyway — HTTP permits disregarding the preference. In practice, sending identity is usually kinder for a browser-facing document, while an API that promises strict negotiation should 406 with a body listing what it supports. What you must not do is send it labelled `Content-Encoding: gzip` when it is not gzipped.
  • Why not just multiply the per-axis scores and take the maximum?
    Because it lets a low-stakes axis outvote a high-stakes one: a 0.8 gzip preference can tip the winner from a German document to an English one. Multiplication also amplifies small server quality-factor errors. Lexicographic priority — media type, then language, then encoding — keeps encoding from ever changing which document the user reads.

saying these in an interview costs you the question

  • Reading q=0 as 'least preferred' and serving it anyway as a best effort
  • Assuming HTTP defines a cross-axis combination algorithm
  • Letting Accept-Encoding preferences change which language or media type is selected
  • Negotiating on several headers without emitting Vary, so a shared cache serves the wrong variant
  • Adding Vary: Accept-Language on hot objects with no thought for cache cardinality

context