Why does a GraphQL API usually have no /v2, and what does that demand of every schema change?
answer
- The caller decides the response shape
- Nothing arrives that was not named
- Grow the schema, never reshape it
- Retire with a directive, not a delete
basics
~20 sEach client names the exact fields it wants, so the server can add types and fields without altering any response already being asked for. Versionless evolution trades a second endpoint for additive change plus deprecation of whatever is retired.
solid answer
~50 sA `/v2` exists because in an endpoint-shaped API the server decides the response shape: change it and every caller sees the change on their next request, so you publish the new shape at a new address. In GraphQL the document decides the shape — the response contains exactly the fields the operation selected and nothing else. A field added to `Statement` this week cannot appear in a response for a document written last year, so the addition is invisible to existing clients. That is what makes one continuously evolving schema practical. It is a design norm, not a rule: the GraphQL specification says nothing about versioning and does not forbid a second endpoint. What the convention demands is that changes be additive by default, and that anything you want to stop supporting be marked with the built-in `@deprecated` directive and left working until its callers are gone.
code
graphql · 23 linesscalar Date
scalar Money
type Statement {
id: ID!
periodStart: Date!
periodEnd: Date!
closingBalance: Money!
interestAccrued: Money
disputeCount: Int!
}
type Query {
statement(id: ID!): Statement
}
# Shipped before the last three fields existed - still valid, still two keys back
query OldStatementScreen($id: ID!) {
statement(id: $id) {
id
closingBalance
}
}go deeper
Be ready to say in one sentence why adding a field is invisible to existing clients: they only ever receive the fields their own document names. Know that retirement is signalled with @deprecated rather than by deleting.
Explain the mechanics rather than the slogan — selection sets decide response keys, so growth is free while reshaping is not. Be able to say that versionlessness is a convention the spec never mandates.
Expect to be pushed on where the additive story fails: a field whose meaning changes under a stable name, and deprecations that are never finished. Show that you treat removal as scheduled work with evidence behind it.
Own the tradeoff: no flag day in exchange for permanent dual-carry obligations. Be ready to argue when a graph would genuinely be better off cutting a parallel type family, and what that costs the registry, the tooling and the client teams.
## Why versions exist at all A version number is a way of saying "the shape of this response changed and I had no way to change it under you." In an endpoint-shaped API the server owns the response body. Drop a key, rename it, or nest it one level deeper, and every caller sees the new body the next time it calls. Since you cannot edit code running on someone else's device, you publish the new body at a new address and keep the old address serving the old one until it is safe to switch it off. GraphQL removes the premise rather than solving the problem. A GraphQL response is assembled from the selection set of the document the client sent: for every field the client named, the server puts one key in the response object, and it puts nothing else there. Two clients hitting the same schema on the same afternoon get different response shapes because they asked different questions. A field the schema gained yesterday is not named in a document written last year, so it cannot appear in that client's response, and that client never needs to learn it exists. ## What the specification actually says about versioning Nothing. The GraphQL specification defines a type system, a document grammar, validation rules and an execution algorithm. It has no notion of a schema version, no version argument, no version header, and no prohibition on running a second endpoint beside the first. Versionless evolution is an ecosystem norm that field selection makes cheap — describe it as a convention in an interview, because calling it a rule is a small but very visible error. The one thing the specification does contribute is the built-in `@deprecated` directive, which gives the convention a vocabulary for "this is on its way out." ## The additive default, concretely Take a bank statements graph. `Statement` is born with an id, a period and a closing balance. Over the following year it acquires interest breakdowns, dispute counts, delivery channels, regulatory flags and a paging argument on its transactions field, until the type carries 37 fields. ```graphql scalar Date scalar Money type Statement { id: ID! periodStart: Date! periodEnd: Date! closingBalance: Money! interestAccrued: Money disputeCount: Int! } type Query { statement(id: ID!): Statement } ``` Every release in that year did one of a small set of things: added a field to an existing type, added a whole type, added an optional argument with a default, added a root field. None of them changed the name, the type or the meaning of a field a live document already selects. A mobile build shipped eleven months ago selects six of the 37 fields and still receives exactly six keys back, in the order it asked for them. It was never redeployed and never needed to be. ## Where "additive is safe" stops being automatic Additive is the default, not a guarantee, and classifying which edits are genuinely safe is a discipline of its own. Two things are worth holding onto at this level. First, the safety argument rests entirely on clients not selecting the new thing — so anything a client is forced to encounter is not covered by it. Second, a change that keeps a field's name and its type but changes what the value *means* is invisible to every diffing tool and every generated type: if `balance` quietly starts meaning the balance at period end rather than the balance right now, nothing anywhere breaks, and every consumer is silently wrong. Versionless evolution has no answer for that, which is why the rule of thumb is to give a new meaning a new field name rather than reuse an old one. ## The other half of the bargain: retire, do not delete If you can only add, the schema grows forever unless something lets you wind fields down. That something is `@deprecated(reason: "...")`, a type-system directive you attach in the schema. It is advisory: a deprecated field is still valid to select and still executes normally. What it does is mark the member for humans and tooling, and hide it from the default introspection listing so new work does not pick it up by accident. Removal is a separate, later act, gated on the field genuinely having no callers left. ## What versionless costs The trade is real. A version cut gives you one coordinated flag day and then a clean schema. Versionless evolution gives you no flag day, and in exchange a permanent low-grade obligation: carry both spellings of anything you rename for as long as an un-upgradable client needs, and actually finish removals rather than letting the schema fill with tombstones. Teams that mark fields deprecated and never remove them get the worst of both worlds — no version boundary and no cleanup — and end up with a type nobody can read.
- Does the GraphQL specification forbid running a versioned endpoint?No. The specification has nothing to say about versioning at all — no version header, no version argument, no rule against serving two schemas at two addresses. Versionless evolution is an ecosystem convention that field selection makes affordable. A team is free to cut a version; it simply loses the single-schema, single-registry, single-tooling story that made the graph attractive, and takes on running both until the old one drains.
- Is every additive schema change automatically safe for existing clients?No — additive is the default posture, not a proof. The safety argument only covers things a client can decline to look at, so an addition a client is forced to encounter is outside it. Deciding which specific edits are safe, dangerous or breaking is its own analysis, and the honest interview answer is "additive by default, then check the change against the breaking-change classes" rather than "adding is always fine."
- How do you change what a field means without renaming it?You do not. A field that keeps its name and its type but changes its semantics breaks every consumer silently: no validation fails, no generated type changes, no diff flags it. Add a new field with the new meaning, deprecate the old one with a reason that explains the difference, and let callers move deliberately. Reusing a name for new semantics is the one change versionless evolution genuinely cannot absorb.
A versioned endpoint is a fixed menu: change a dish and you must print a new menu. A GraphQL schema is a pantry — customers name what they want, so stocking a new ingredient changes nobody's order.
saying these in an interview costs you the question
- Claims the GraphQL spec bans versioning an API
- Says adding a field breaks clients who did not request it
- Thinks a typed schema makes breaking clients impossible
- Believes @deprecated removes the field from the schema
- Proposes a new endpoint for every schema change
- Reuses a field name for a changed meaning