What happens to GraphQL subscription events published while a subscriber is disconnected?
answer
- the stream lives only while the socket does
- the specification stops at execution
- neither subprotocol names a buffer
- resubscribing starts a new stream
- silence and a gap look the same
basics
~10 sThey 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 sA 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 linestype Subscription {
sensorReading(paddockId: ID!): SensorReading!
}
type SensorReading {
id: ID!
sensorId: ID!
soilMoisture: Float!
batteryPercent: Int!
recordedAt: String!
}go deeper
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.
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.
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.
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