skip to content

questions

5

A service must mark which version of its payload shape a message carries. Where can that version identifier live, and what does each placement cost?

level: middleimportance: must knowfreq 68%

answer

  1. the reader must find it before decoding
  2. body, media type, or address
  3. who else needs to read the marker?
  4. negotiation needs a two-way exchange
  5. the cost moves, it never vanishes

basics

~20 s

Three placements are common: a version field inside the payload, a negotiated media type, and a version segment in the address. Each moves the cost elsewhere - routing and caching, client effort, or resource identity - and none removes it.

solid answer

~50 s

A reader has to find the version marker *before* it interprets the rest of the bytes, so it can sit in only a few places. A `version` field inside the payload envelope travels over any channel, including a queue or an archived file, but the reader must decode part of the message to see it and intermediaries cannot route or cache on it. A negotiated media type puts the version in the request's `Accept` header and echoes it in `Content-Type`: one address, caches can key on the varying header, but simple clients and tooling often cannot send a custom media type. A version segment in the address is visible everywhere - logs, routing, caches - at the price of one resource having many identities and clients hardcoding one of them. Pick by which channels you actually have and who needs to read the marker.

code

http · 6 lines
http
GET /orders/42
Accept: application/vnd.orders+json; version=2

HTTP/1.1 200 OK
Content-Type: application/vnd.orders+json; version=2
Vary: Accept

go deeper

for a junior

Recall that a versioned payload says somewhere which shape it is, and that the receiver has to be able to find that marker before reading the rest.

for a middle

Explain the three placements and what each costs: body fields are invisible to routers and caches, negotiated media types need capable clients, address segments give one resource several identities.

for a senior

Show the operational consequence: which placement lets you route, cache and measure per version, what the default is for callers that ask for nothing, and how an unknown version fails.

for a principal

Frame it as a contract-surface decision across a caller base you do not control - the marker you choose decides whether future shut-offs are a routing change or a code change in every consumer.

## What the version identifier is for A version marker answers exactly one question for whoever receives the bytes: **which contract do these bytes implement?** It is not itself a compatibility mechanism. Compatibility rules decide whether a change needs a new version at all; the marker only tells a reader which one it is holding, so that it can pick the right interpretation or refuse. That purpose imposes one hard constraint: the marker must be **findable before the payload is interpreted**. Anything that requires you to already know the shape in order to locate the version is circular. In practice that leaves three places. ## The three placements - **Inside the payload.** A small `version` field (or a schema id) in the message envelope, read before the rest. It travels with the bytes over any channel - a synchronous call, a queued message, a file written to an archive - because it *is* the bytes. - **In a negotiated media type.** The caller states what it wants (`Accept: application/vnd.orders+json; version=2`) and the responder states what it sent (`Content-Type: ...; version=2`). The resource keeps one address and the version becomes a property of the representation, which is exactly what a media type is for. - **In the address.** A path segment (`/v2/orders/42`) or a query parameter. The version is then part of the identifier, visible to every layer that sees a URL without parsing anything. ## What each one costs | Placement | Visible to routers and caches | Works with no request/response exchange | Client effort | Resource identity | |---|---|---|---|---| | Field in payload | No - the body must be decoded first | Yes | Lowest | One identity | | Negotiated media type | Yes, via the request header a cache varies on | No - needs a counterpart to negotiate with | Highest - custom headers | One identity | | Segment in address | Yes, trivially | Partly - only where an address exists | Low | One resource, many identities | The costs in detail: - **Payload field.** An edge proxy, a cache or a router cannot act on it, so version-aware routing has to happen inside the service. Bodies also have to be decoded far enough to find the field, which matters when the two versions are not mutually decodable. - **Negotiated media type.** It is the most faithful model - the version is a property of the representation, not of the thing - but it leans on the whole chain honouring a non-default media type: form posts, browser address bars, hand-written scripts and some intermediaries will not send one. A default must therefore exist, and picking "newest" as the default silently breaks anyone who never sent a header. - **Address segment.** The same order now has two addresses, so anything that compares, stores or links identifiers has to be told they are the same thing. It also invites version numbers to leak into client code as string literals. ## Rules that survive whichever you pick 1. **Bump only when a reader must act.** A version that changes for additive, compatible edits trains consumers to ignore it, which costs you the one time it mattered. 2. **One version per contract, not per field.** Per-field versioning multiplies the states a reader must handle and nobody can enumerate them. 3. **The marker is cheap to read or it is not a marker.** A fixed-position field or a header, never something recovered by sniffing the shape. 4. **A reader that meets a version it does not know must fail loudly**, not guess. Guessing turns a rollout bug into silent data corruption. ## Choosing Work through the channels you actually have: 1. **Is there a request/response exchange at all?** If some consumers read from a queue or from files, negotiation is unavailable to them and the version has to be in the payload regardless of what the synchronous path does. 2. **Does anything between the parties need to see it** - routing, caching, rate limits, analytics? If yes, it must be in the address or in a header, not the body. 3. **How capable are the clients?** A wide, mixed, partly hand-rolled caller base pushes towards the address; a small set of generated clients can carry media types comfortably. 4. **How often will you really break?** If the honest answer is "rarely, and additively in between", the cheapest scheme that the whole chain understands wins, and the mechanism that matters is not the marker but the migration behind it. Most long-lived systems end up with **both**: a coarse marker where routing can see it, and a finer schema id inside the payload for the readers that decode it. That is not redundancy, as long as one of them is authoritative and the other is derived from it.

  • If the version lives only in a negotiated media type, what must the service do for a caller that sends no preference at all?
    It must define an explicit default and publish it. Defaulting to the newest version means every future release silently changes what those callers receive, so the safer default is the oldest version still supported, with the deprecation clock applied to it like any other version.
  • Why is a per-field version number a worse design than one version per contract?
    Because the number of combinations a reader must handle is the product of the per-field versions, not their sum. Nobody can enumerate or test that set, and no single number then describes what a producer is emitting, which makes both rollout tracking and shut-off decisions impossible.
  • Should the version number carry meaning, like a major/minor split?
    Only if the split is enforced. A two-part number is useful when the major part changes exactly when a reader must act and the minor part never does; if compatible and breaking edits both bump the same part, the structure is decoration and consumers learn to ignore it.

A version marker is the label on the outside of a parcel, not the packing list inside it: anyone in the delivery chain can act on the label, while the packing list only helps once you have already opened the box.

saying these in an interview costs you the question

  • Claims a version segment in the address is always the wrong choice
  • Assumes every client can send a custom media type header
  • Believes a cache or router can act on a version field buried in the body
  • Treats the version marker as if it made changes compatible by itself
  • Bumps the version for purely additive, compatible changes
  • Lets an unknown version fall through to the newest reader silently
open as a page

A live endpoint must replace a field with an incompatible one while old callers keep sending the old shape. What does an expand-and-contract migration do in each phase?

level: seniorimportance: must knowfreq 60%

basics

~20 s

Expand-and-contract ships one incompatible change as a sequence of compatible ones: add the new form beside the old, write both, move readers to the new with a fallback, backfill what already exists, stop writing the old, then remove it.

open as a page

Your team keeps two versions of one endpoint's payload running side by side for a year. What actually doubles, and what does not fork?

level: seniorimportance: should knowfreq 46%

basics

~20 s

What doubles is the surface: contracts, tests, docs, client support and every later change, which must land in both. What does not fork is the data and the behaviour behind them, so a change of meaning reaches old callers however many shapes you serve.

open as a page

As the lead, how do you set the deprecation window for a retired payload version, signal it, and decide when it is safe to switch off?

level: principalimportance: should knowfreq 36%

basics

~20 s

Set the window from the caller population's slowest realistic cycle, not from habit; signal it both machine-readably in responses and through a human channel; and decide the shut-off from per-caller usage measured over a full cycle, escalating through warnings and brownouts rather than a single date.

open as a page

A payload version travels on a queue and into archived files, where no request-and-response exchange exists. Which versioning strategy still works, and why?

level: seniorimportance: nice to knowfreq 26%

basics

~20 s

Only a self-carried marker works: the version must sit in the bytes, because negotiation needs a counterpart to ask and an address to ask it at, and a consumer reading a queued message or an archived file has neither.

open as a page