skip to content

Transports & Subscriptions

Transport-agnostic on paper and almost always HTTP plus a socket in practice. Subscriptions are the part the specification leaves to the implementer, which is exactly why interviewers push on them.

part ofGraphQLoverview, primer and where to startread it →
on this pageshow

questions

page 1 of 2

What happens to GraphQL subscription events published while a subscriber is disconnected?

level: juniorimportance: must knowfreq 44%

answer

  1. the stream lives only while the socket does
  2. the specification stops at execution
  3. neither subprotocol names a buffer
  4. resubscribing starts a new stream
  5. silence and a gap look the same

basics

~10 s

They are lost. Neither the GraphQL specification nor the WebSocket subprotocols defines buffering, replay or acknowledgement, so a reconnecting client subscribes again from the present moment and is never told what it missed.

solid answer

~50 s

A subscription's stream exists only while the connection does. When the socket closes the server unsubscribes from the source event stream behind the operation; publishers keep publishing and nothing is retained for the absent subscriber. On reconnect the client starts the operation again, which creates a **new** source event stream beginning at that instant. Nothing in the GraphQL specification, in `graphql-ws`, or in the older `subscriptions-transport-ws` defines a buffer, a resume token, a redelivery rule or an application-level acknowledgement — so there is no message meaning *send me what I missed*, and no result carries a position that would reveal a gap. The client knows how long it was disconnected and nothing else: an empty gap and a gap containing 40 events look identical. Any replay you see is a server's own convention, not conformance.

code

graphql · 11 lines
graphql
type Subscription {
  sensorReading(paddockId: ID!): SensorReading!
}

type SensorReading {
  id: ID!
  sensorId: ID!
  soilMoisture: Float!
  batteryPercent: Int!
  recordedAt: String!
}

go deeper

for a junior

Recall the one fact everything else here rests on: a subscription is a live channel, and events published while you were disconnected are gone. Be able to say that no GraphQL specification or subprotocol promises otherwise.

for a middle

Explain the mechanics — the source event stream is torn down with the connection, a resubscribe creates a new one starting now, and no result carries a position that would let the client detect the discontinuity.

for a senior

Show that you design for it: the failure is not a crash but a field that quietly keeps returning stale data, so your answer should reach resync on reconnect and payloads that repair themselves.

for a principal

Own the framing that this is a scope decision, not a defect, and that any durability guarantee is one your organisation defines, operates and tests — never something conformance gives you for free.

## Where the specification stops The GraphQL specification defines `subscription` as a third operation type beside `query` and `mutation`, and it defines how one is executed: the server resolves the operation's single root field to a **source event stream**, and then, for every event that stream produces, runs one ordinary execution of the selection set and emits one result. That is the whole of it. The specification says nothing about how those results reach a client, nothing about how long the stream lives, nothing about what happens when it ends unexpectedly, and nothing about durability. Transport is deliberately out of scope, which is also why the specification never mentions WebSockets. ## What the subprotocols add, and what they pointedly do not In practice a subscription travels over a WebSocket under one of two named subprotocols: the modern `graphql-ws` (whose WebSocket subprotocol identifier is `graphql-transport-ws`) or the older `subscriptions-transport-ws`. Both describe how a connection is initialised, how a client starts and stops an operation, and how the server frames each result and signals that a stream has ended. Neither describes a buffer, a replay window, a resume token, a redelivery rule, or an acknowledgement that the client processed a result. There is no message a reconnecting client can send meaning *give me what I missed*, because there is nothing on the server that kept it. This is a scope decision, not an oversight: the subprotocols frame a live channel and leave durability to the application. ## What actually happens when the socket drops 1. The connection closes. The server tears down every operation multiplexed on that connection, which means unsubscribing from each source event stream. Publishers keep publishing; nothing is retained on behalf of the subscriber that is no longer there. 2. The client notices a closed socket, reconnects, and starts the operation again. That produces a **brand-new source event stream**, which begins at the instant of subscribing. 3. Events published between step 1 and step 2 exist nowhere in the request path. They were delivered to whoever was connected and dropped for whoever was not. ## What the client can know, and what it cannot The client knows the connection closed and reopened, so it knows the *duration* of the gap. It does not know how many events fell into it, which objects they concerned, or whether it was empty. A 27-second reconnect and a quiet 27 seconds are indistinguishable from the client side, because no result carries a position and no message announces a discontinuity. ## The failure this produces A dashboard over a farm sensor graph subscribes to `sensorReading(paddockId:)` across 19 paddocks and writes each result straight into the view. Sensor `sf-0417` reports about every four minutes. The socket drops at 14:01:58 and the client is back at 14:02:25; the reading published at 14:02:11 is gone. The tile keeps showing `soilMoisture: 41.6`, the value from before the gap. Nothing errors, nothing is missing, no spinner appears — the field simply returns stale data. If that sensor's battery had died at 14:02:11, the tile would stay wrong until someone reloaded the page. That is the characteristic shape of this failure: silent, indefinite staleness in a UI that looks healthy. ## "But WebSockets are reliable" — the acknowledgement trap TCP retransmits and orders bytes **while the connection lives**; a close is precisely the case where it stops promising anything. And byte delivery is not processing: a result that reached a browser tab which was closed mid-render was delivered and never applied. Neither GraphQL subprotocol defines an application-level acknowledgement, so the server cannot tell those apart and never learns what a subscriber consumed. If you need that knowledge, you build it — the client calls a mutation recording what it applied, and the server starts keeping per-subscriber state it otherwise never keeps. ## Slow but still connected The sibling case is a subscriber whose socket is open but not draining. Nothing in GraphQL or in either subprotocol defines what happens; the server's outbound buffer fills and the implementation either drops results or closes the connection. Both outcomes are silent from the client's point of view and land you in the same place: a gap you cannot see. ## What to say in an interview State it as a property, not a complaint. A GraphQL subscription is a live notification channel, not a durable log. The specification stops at execution; the subprotocols stop at framing; replay, acknowledgement and retention are things you design on top, and any server that offers them is offering its own convention, not conformance to anything. Then say what you do about it: refetch current state on reconnect, shape each result so the next one repairs the gap, and add an explicit application-level cursor only for data where every transition matters. Candidates lose ground here by asserting a delivery guarantee the protocol never made — the honest answer is that GraphQL makes no delivery promise at all across a disconnect.

  • Can a subscriber acknowledge that it processed a result, so the server knows what was consumed?
    Not through GraphQL. Neither WebSocket subprotocol defines an application-level acknowledgement, and the transport only tells you bytes arrived — a result delivered to a tab that closed mid-render was delivered and never applied. If you need that knowledge you build it yourself, typically as a mutation the client calls to record what it applied, and the server then has to keep per-subscriber state it otherwise never keeps.
  • What if the subscriber is still connected but too slow to drain the stream?
    Nothing in GraphQL or in either subprotocol defines the behaviour, so it is implementation-defined: the server's outbound buffer fills and the runtime either drops results or closes the connection. Both are silent from the client's side and produce the same invisible gap, which is why a slow consumer needs the same resync discipline as a disconnected one.
  • How does a client tell an empty gap from a lossy one?
    It cannot, from the protocol alone. No result carries a position and no message announces a discontinuity, so a quiet 27 seconds and a 27-second outage look identical. The only signals are ones you add in the schema — a monotonic timestamp or version on each payload the client can compare against what it already holds.

It is a live radio broadcast, not a podcast feed: while your receiver is off the transmitter keeps transmitting, and turning it back on gives you the programme from this second, with no way to ask what played in between.

saying these in an interview costs you the question

  • Says the server replays missed events on resubscribe
  • Calls GraphQL subscriptions at-least-once delivery
  • Assumes the WebSocket subprotocol buffers during a drop
  • Thinks TCP delivery proves the client processed a result
  • Believes a reconnect gap is always visible to the client
  • Trusts event-fed client state without any refetch

context

open as a page

GraphQL defines no file type, so how does a client send a file with an operation?

level: juniorimportance: must knowfreq 52%

basics

~20 s

The GraphQL specification defines no binary scalar and no file transport. Two conventions fill the gap: a multipart request that carries the operation and the bytes together, or uploading to storage separately and passing a reference back through a mutation.

open as a page

Why can a GraphQL response carry HTTP 200 OK and still report that the operation failed?

level: juniorimportance: must knowfreq 68%

basics

~20 s

The HTTP status describes the transport, not the GraphQL result. A request the server parsed and executed answers 200 even when execution produced errors, and under the legacy application/json response type the GraphQL over HTTP draft requires 200 for every well-formed request.

open as a page

What does a GraphQL POST request body contain, and what is each key for?

level: juniorimportance: must knowfreq 74%

basics

~20 s

A GraphQL POST body is a JSON object with up to four keys: query, the operation document text; variables, a map of variable values; operationName, naming which operation in the document to run; and extensions, an implementation-defined map.

open as a page

How does a GraphQL subscription operation reach a server that streams results as text/event-stream?

level: juniorimportance: must knowfreq 38%

basics

~20 s

The operation travels in the HTTP request that opens the stream: a POST body carrying query and variables, or a GET with the same values as URL parameters. An event stream is one-way, so nothing can be sent upstream afterwards.

open as a page

Which WebSocket subprotocol identifiers do GraphQL subscription clients offer?

level: juniorimportance: must knowfreq 40%

basics

~10 s

Two identifiers are in use: graphql-transport-ws, the current one, and graphql-ws, the legacy one carried by the subscriptions-transport-ws protocol. The names are crossed, and neither subprotocol is defined by the GraphQL specification.

open as a page

What messages must a GraphQL-over-WebSocket client and server exchange before the first subscribe?

level: juniorimportance: must knowfreq 62%

basics

~20 s

The client sends connection_init as its first message, optionally carrying credentials in a free-form payload object, and waits for the server's connection_ack. Only after that acknowledgement may it send a subscribe message; anything earlier is closed, not answered.

open as a page

In GraphQL, why does one event delivered to 3,000 subscribers cost 3,000 executions?

level: middleimportance: must knowfreq 70%

basics

~20 s

Because each subscriber has its own document, variables and context, the server executes the subscription's selection set once per subscriber and serializes a different payload for each. Only the source event is shared; everything downstream of it multiplies.

open as a page

What does a GraphQL server hold in memory for each open subscription, and for how long?

level: juniorimportance: should knowfreq 44%

basics

~10 s

A 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.

open as a page

What do the @defer and @stream directives change about a single GraphQL response?

level: juniorimportance: should knowfreq 34%

basics

~20 s

One operation answers in several payloads instead of one. The server returns the fields it can answer immediately, then sends a deferred fragment's data, or a streamed list's remaining items, as they finish. Neither directive is in a released specification edition.

open as a page

What is GraphQL transport-level batching, where one HTTP body holds an array of operations?

level: juniorimportance: should knowfreq 42%

basics

~20 s

Transport-level batching puts several GraphQL operations in one HTTP body as a JSON array; the server replies with an array of results in the same order. It is a convention - the GraphQL over HTTP specification defines the body as one map.

open as a page

How does a GraphQL client resync its data after a subscription reconnects?

level: middleimportance: should knowfreq 40%

basics

~10 s

Resubscribe first, then run a query for current state and apply the buffered events on top of it. The subscription is a change notification; a query is the source of truth on every reconnect.

open as a page

In a GraphQL multipart request, what do the operations and map fields do?

level: middleimportance: should knowfreq 36%

basics

~20 s

The operations field holds the JSON operation with null wherever a file belongs. The map field binds each numbered file part to the variable path that null sits at. The server substitutes the bytes into the variables before executing.

open as a page

How does a client reassemble a deferred GraphQL response from its incremental payloads?

level: middleimportance: should knowfreq 26%

basics

~20 s

It keeps the initial payload's tree and merges each later payload into it at the response path that payload carries — a list of field names and list indices counted from the root. A payload whose hasNext is false ends the response.

open as a page

What does the application/graphql-response+json media type change about HTTP status codes?

level: middleimportance: should knowfreq 38%

basics

~20 s

It makes the status meaningful again for failures before execution. The GraphQL over HTTP draft keys the status to whether the response has a data entry: no data entry means 4xx or 5xx, while a data entry present — even null — means 200.

open as a page

In a batched GraphQL request, what does the single HTTP status code tell you about each operation?

level: middleimportance: should knowfreq 44%

basics

~20 s

Nothing about any individual operation. One HTTP status covers the whole request, and each element of the response array carries its own result envelope, so a client must inspect every element by position to learn which operations actually succeeded.

open as a page

How is a GraphQL operation encoded in a GET request, and which operations may travel that way?

level: middleimportance: should knowfreq 47%

basics

~20 s

Over GET the parameters move into the URL query string: query as the percent-encoded document, operationName as a plain string, and variables and extensions as JSON serialized to a string and then percent-encoded. GET carries query operations only.

open as a page

How can one GraphQL subscription endpoint serve both graphql-transport-ws and graphql-ws clients?

level: middleimportance: should knowfreq 33%

basics

~20 s

Register both identifiers on the endpoint and dispatch per connection on whichever one was negotiated. Each identifier gets its own message-vocabulary adapter over one shared subscription execution, so the schema and resolvers are written once.

open as a page

In the graphql-ws subprotocol, how do next, error and complete messages relate to one subscription id?

level: middleimportance: should knowfreq 54%

basics

~20 s

Every stream carries a client-chosen id. next delivers one execution result and may repeat indefinitely; error delivers an array of errors and ends the stream; complete ends it normally and travels in both directions. After either terminator, nothing more is sent under that id.

open as a page

How do you shape a GraphQL subscription payload so one missed event is not permanent?

level: seniorimportance: should knowfreq 33%

basics

~20 s

Send the identified object's current state, or a hint the client refetches by id, rather than a delta. Then the next event overwrites whatever was missed, so a gap costs freshness for one interval instead of correctness forever.

open as a page

A GraphQL subscription executes for 3,000 subscribers, then an authorization check empties most payloads. How would you cut that cost?

level: seniorimportance: should knowfreq 48%

basics

~20 s

Move the predicate earlier. Narrow what each node receives at the event source, then drop events per subscription using the stored variables and context before executing, so only subscribers who will actually receive a payload cost an execution.

open as a page

When would you take file bytes out of a GraphQL API and use signed upload URLs instead?

level: seniorimportance: should knowfreq 41%

basics

~20 s

When files are large, numerous, or crossing many hops on the way in. A mutation mints a short-lived signed URL, the client uploads straight to storage, and a second mutation attaches the stored object by reference.

open as a page

Why does one slow operation in a batched GraphQL request delay every other result in it?

level: seniorimportance: should knowfreq 36%

basics

~20 s

Because all the results travel in one JSON array in one HTTP response, which cannot be delivered until the last member has finished. Running members concurrently shortens the total wait, but the client still sees nothing until the slowest one completes.

open as a page

A GraphQL endpoint answers every read and write as a POST to one URL. How do you stop an automatic retry duplicating a write?

level: seniorimportance: should knowfreq 41%

basics

~20 s

Nothing outside a GraphQL request body distinguishes a read from a write — same URL, same method, same headers — so no intermediary can retry safely. Move retry decisions to the code that authored the operation, or make the write repeat-safe.

open as a page

A GraphQL subscription streams over SSE for hours — how do you handle the credential that authorized it expiring?

level: seniorimportance: should knowfreq 33%

basics

~20 s

The credential is checked once, on the opening request, and nothing can travel upstream to refresh it. The usual answer is to bound each stream's lifetime to the credential's remaining life, close it with jitter, and let the client reconnect.

open as a page

A GraphQL subscription socket opens but no events ever arrive — how do you diagnose it?

level: seniorimportance: should knowfreq 45%

basics

~10 s

Check the negotiated WebSocket subprotocol on the open connection first. An empty or mismatched identifier explains an open-but-silent socket more often than resolvers or authorization do, and it costs one field to rule out.

open as a page

A GraphQL WebSocket endpoint is closing sockets with codes 4401 and 4408 — what does each mean?

level: seniorimportance: should knowfreq 44%

basics

~20 s

4408 means the client never sent connection_init inside the server's initialisation window; 4401 means it sent an operation before the connection was acknowledged, or its init payload was rejected. Both are the graphql-ws subprotocol's own application-range codes, not transport ones.

open as a page

How do you decide how much replay a GraphQL subscription API should promise its clients?

level: principalimportance: should knowfreq 26%

basics

~20 s

Decide per root field, and stop at the cheapest rung that fits the data: no replay with refetch on reconnect, self-healing payloads, or an explicit cursor argument with a stated retention window you can actually operate and test.

open as a page

Does the GraphQL specification define how a request travels over HTTP?

level: middleimportance: nice to knowfreq 27%

basics

~20 s

No. The GraphQL specification is transport-agnostic: it defines the language, the type system and the execution algorithm, and stops at a result. HTTP serving is described by a separate document, GraphQL over HTTP, still a working draft.

open as a page

In GraphQL subscriptions over SSE, what does one multiplexed stream buy over one stream per operation?

level: middleimportance: nice to knowfreq 24%

basics

~20 s

One held-open response instead of one per subscription. The price is a control channel: the client subscribes and stops through separate ordinary HTTP requests naming the stream, every result must carry an operation identifier, and the stream becomes node-bound state.

open as a page

showing 1–30 of 35