What does a GraphQL server hold in memory for each open subscription, and for how long?
answer
- A query forgets; a stream cannot
- Every event runs the same selection set
- Variables and identity fixed at subscribe time
- Lives beside the socket, on one node
- Only the client can rebuild it
basics
~10 sA server keeps, per open subscription: the subscriber's parsed document, its coerced variables, the client's operation id, and the request context. That record lives until the client stops the operation or the connection ends.
solid answer
~50 sA query is parsed, executed, serialized and forgotten — its state dies with the response. A subscription cannot work that way. In the GraphQL specification a subscription operation yields a **response stream**: one response per source event, each produced by executing the same selection set again. So the server has to keep that selection set, the variables coerced at subscribe time, the operation id the client used to start the stream, and whatever request context the resolvers read — identity, tenant, locale — plus a cancellation handle on the event source. All of it is retained until the client stops the operation, the socket closes, or the server tears the stream down. Nothing in the specification says how to store any of this; the shape of the record is implementation convention. The practical consequence is that this state is process-local: it lives on the node holding that subscriber's connection.
code
graphql · 7 linessubscription SeatMap($flightId: ID!, $cabin: Cabin!) {
seatStateChanged(flightId: $flightId, cabin: $cabin) {
seatNumber
status
heldUntil
}
}go deeper
Be ready to list what is retained: the parsed document, the variables, the operation id, the request context and a handle on the event source, freed only when the client stops the stream or the connection ends. Recall that a query keeps none of this.
Explain why retention is forced: every source event re-executes the same selection set, so the document and variables must survive. Be able to say which parts are per-subscriber and which can be shared, such as an interned parsed document.
Show you reason about the aggregate. Interviewers expect you to connect per-subscription state to node-local memory, to the resubscribe wave a rolling restart triggers, and to context that goes stale because it was snapshotted at subscribe time.
Own the consequence that a subscription is durable client-side and ephemeral server-side. Be ready to argue what recovery you promise after a node loss, and who pays for the reconnect storm — the client's retry policy or the server's spare capacity.
## A query's state dies with the response; a subscription's does not For a query or a mutation, a server parses the document, validates it, executes it once, serializes the result, and forgets everything. Nothing about that request has to survive the response, which is why a stateless request/response tier scales the way it does. A subscription breaks that. The GraphQL specification models a subscription operation as producing a **response stream** rather than a single response: a source event stream is created from the operation's single root field, and every event on it is mapped to a response by executing the subscription's selection set again with that event as the root value. "Executing the selection set again" is the whole point — it is only possible if the server still *has* the selection set. The document therefore cannot be discarded after the first delivery, and neither can anything else the next execution will need. ## What the per-subscription record actually holds In practice the record a server keeps for one open subscription contains: - **The parsed, validated document** — usually the AST, not the raw text, because re-parsing on every event would be absurd. - **The coerced variable values**, fixed at subscribe time. A subscription's arguments do not change once the stream is running; a client that wants different arguments starts a different subscription. - **The operation name**, when the document defines more than one operation. - **The client-chosen operation id** — the identifier the client used when it started this stream and will use to stop it. Without it the server cannot address later messages to the right stream. - **The request context**: identity and entitlements, tenant, locale, trace identifiers, anything a resolver reads. Note that this snapshot was taken when the subscription started and, unless the server refreshes it, it can drift from reality while the stream runs. - **A handle on the source event stream** plus the cancellation token used to detach from it. - **Output plumbing**: the session or socket to write to, and often a small outbound queue. ``` SubscriptionRecord: operationId = "seatmap-7" # chosen by the client document = <parsed AST> # re-executed on every event variables = { flightId: "QX414-2026-11-08", cabin: "ECONOMY" } context = { agentId: 8812, entitlements: [...], locale: "en-GB" } sourceHandle = <cancellable subscription to the seat-state stream> sink = <connection + outbound queue> ``` ## The multiplication is what makes it a scaling topic One record is trivially cheap. The number of them is not. A single connection can carry many subscriptions, each with its own id: an airline's seat-map console opens one socket and starts six streams — seat holds, cabin availability, price changes, gate changes, an operational alert feed, and a per-agent task queue. A sale with 3,187 concurrent watchers on one flight is not 3,187 records, it is 3,187 times however many streams each of them started. The fat part of each record is usually the parsed document, which is why servers commonly intern the AST by a hash of the document text, so thousands of subscribers sending byte-identical documents share one parsed copy and keep only their own variables, context and id. That is an implementation optimisation, not anything the specification requires. ## The record is pinned to one node, and only the client can rebuild it This state lives in the process holding the connection. It is not in a database, it is not replicated, and no other node can serve that subscriber's next event without it. Two consequences follow, and both come up in interviews: 1. **An event produced anywhere must reach the node that holds the record.** That is why a multi-node deployment needs some event backbone at all; the placement mechanics belong to the horizontal-scaling discussion, but the reason starts here. 2. **A restart destroys every record on that node.** The only durable copy of a subscription is the client's ability to send the operation again, so a rolling deploy converts into a wave of reconnect-and-resubscribe traffic, all of it arriving at once. Capacity planning for subscriptions has to include that wave, not just the steady state. ## Specified versus conventional Specified: a subscription operation has exactly one root field, it produces a response stream, and each source event is mapped to a response by executing the selection set with that event. Conventional: everything about *how* the state is stored — the record layout, AST interning, context refresh, the operation id itself (that identifier comes from the WebSocket subprotocol, not from the GraphQL specification), and the reconnect behaviour. A candidate who says "the spec requires the server to keep the document" has the layering slightly wrong: the specification requires re-execution, and keeping the document is the only sane way to obey that.
- The subscriber's permissions change while the stream is open. What happens?By default, nothing — the context was snapshotted when the subscription started, so the stream keeps running under the entitlements the subscriber had then. Keeping it correct is the server's job: re-check the relevant entitlement per event, or consume a revocation event and tear the stream down. Neither is specified, and a stream that outlives its authorization is a real defect, not a quirk.
- Can a client change a running subscription's variables?No. Variables are coerced when the operation starts and the record holds those values for the life of the stream. Changing arguments means stopping that operation and starting a new one with a new id. This is why a client that filters by a value the user can edit — a cabin selector, say — churns subscriptions as the user clicks.
- Why do thousands of identical subscriptions not cost thousands of parsed documents?Because servers commonly intern the parsed document by a hash of its text, so byte-identical documents share one AST and each subscriber keeps only its own variables, context, id and sink. That is an implementation optimisation rather than specified behaviour, and it only helps when clients send stable document text rather than string-built queries.
It is a standing order rather than a purchase: the shop keeps your form, your address and your preferences on file and re-runs it every time stock arrives, instead of handing over one bag and forgetting you.
saying these in an interview costs you the question
- Thinks the server stores the last response and diffs it
- Believes subscription state is shared across all nodes
- Says the document is discarded after the first event
- Assumes variables can be updated on a live subscription
- Treats the identity snapshot as always current
- Ignores the resubscribe wave after a restart