How do GraphQL subscriptions work under the hood with WebSockets?
See how GraphQL subscriptions stream real-time updates over WebSockets using graphql-ws, PubSub, and async iterators, from handshake to event push.
Expected Interview Answer
GraphQL subscriptions push server-to-client updates over a long-lived WebSocket connection: the client opens a socket, sends a subscription operation, and the server streams a result each time the subscribed event fires until either side unsubscribes.
Unlike queries and mutations that use a request/response cycle, subscriptions keep a persistent connection using a subprotocol such as graphql-ws. After a handshake and optional connection-init for auth, the client sends a subscribe message; the server's subscribe resolver returns an async iterator, typically backed by a PubSub system. Each time a mutation or backend event publishes to a topic, the iterator yields, the payload is run through the resolver, and a next message is sent over the socket. A complete or stop message ends the stream.
- Real-time server push without client polling
- A single persistent connection multiplexes many subscriptions
- PubSub decouples event producers from subscribers
- Async iterators model the event stream cleanly
- Auth can happen once at connection init
- Scales across servers with a shared broker like Redis
AI Mentor Explanation
Picture subscribing to live ball-by-ball alerts rather than refreshing the scoreboard yourself. You tell the broadcaster once which match you care about, keep the line open, and every delivery they push you the update instantly. When the innings ends or you hang up, the alerts stop. The WebSocket is that open line, and each ball bowled is an event published to everyone tuned in.
Step-by-Step Explanation
Step 1
Open the WebSocket
The client upgrades HTTP to a WebSocket and speaks a subprotocol like graphql-ws.
Step 2
Connection init
Client sends connection_init (often with an auth token); server acks with connection_ack.
Step 3
Send subscribe
Client sends a subscribe message carrying the subscription query and an operation id.
Step 4
Return an async iterator
The subscribe resolver returns an async iterator, usually from pubsub.asyncIterator(topic).
Step 5
Publish events
A mutation or backend process calls pubsub.publish(topic, payload), yielding the iterator.
Step 6
Stream and close
The server sends a next message per event; a complete or client stop ends the stream.
What Interviewer Expects
- That subscriptions use a persistent WebSocket, not request/response
- Knowledge of the graphql-ws subprotocol and its message types
- The role of PubSub and async iterators
- How events published by mutations reach subscribers
- Auth at connection_init and scaling with a shared broker
Common Mistakes
- Confusing subscriptions with polling or short-lived HTTP
- Referencing the deprecated subscriptions-transport-ws as current best practice
- Forgetting a PubSub or broker is needed to fan out events
- Not handling auth during the connection handshake
- Assuming in-memory PubSub works across multiple server instances
Best Answer (HR Friendly)
“GraphQL subscriptions let the server push live updates to the client over a connection that stays open, like a phone line that stays connected. The client subscribes once, and whenever something changes on the server, the new data is sent down that open line instead of the client repeatedly asking for it.”
Code Example
const { PubSub } = require('graphql-subscriptions');
const pubsub = new PubSub();
const MESSAGE_ADDED = 'MESSAGE_ADDED';
const resolvers = {
Mutation: {
postMessage: (parent, { text }, context) => {
const message = { id: Date.now(), text };
pubsub.publish(MESSAGE_ADDED, { messageAdded: message });
return message;
},
},
Subscription: {
messageAdded: {
subscribe: () => pubsub.asyncIterator([MESSAGE_ADDED]),
},
},
};subscription OnMessageAdded {
messageAdded {
id
text
}
}Follow-up Questions
- How do you scale subscriptions across multiple server instances?
- What replaced subscriptions-transport-ws and why?
- How is authentication handled on a subscription connection?
- When would you use SSE or live queries instead of subscriptions?
MCQ Practice
1. What transport do GraphQL subscriptions typically use?
Subscriptions keep a long-lived WebSocket open so the server can push each new event to the client.
2. What does a subscription resolver's subscribe function usually return?
The subscribe function returns an async iterator, commonly from a PubSub, that yields a payload per published event.
3. Which subprotocol is the current standard for GraphQL over WebSockets?
graphql-ws is the actively maintained protocol; subscriptions-transport-ws is deprecated.
Flash Cards
How do subscriptions differ from queries? — Queries use request/response; subscriptions stream server pushes over a persistent WebSocket.
What does a subscribe resolver return? — An async iterator, typically pubsub.asyncIterator(topic), that yields on each event.
What triggers a subscription event? — A publish call (e.g. pubsub.publish) from a mutation or backend process.
What is the current WebSocket subprotocol? — graphql-ws; subscriptions-transport-ws is deprecated.
How do subscriptions scale across servers? — Back PubSub with a shared broker like Redis so events fan out to all instances.