Why can't one GraphQL subscription operation watch two event streams at once?
answer
- One operation, one source
- The restriction applies at the root only
- Counted after fragment spreads flatten
- Nothing to stream from introspection
- Two operations, or a union payload
basics
~20 sBecause a subscription operation's root selection set must collapse to exactly one field, and that field is what creates the event source. Two streams means two subscription operations, or one root field emitting a union of event shapes.
solid answer
~50 sA validation rule in the GraphQL specification requires the fields collected on a subscription operation's root to number exactly one, and that field must not be an introspection meta-field. The reason is that the root field *is* the stream: resolving it produces the source of events, and one operation is defined to map to one source. The count is taken after fragment spreads are flattened, so hiding a second root field inside a fragment fails validation just the same. The restriction is on root fields only — nesting beneath that single field is unrestricted. A client on a hospital appointment graph needing both slot releases and cancellations therefore has two honest options: run two subscription operations, or design one coarse root field such as `clinicEvents(clinicId: ID!)` whose payload is a union covering both event shapes. Which you pick is schema design, not a specification rule.
code
graphql · 4 linessubscription ClinicFeed($clinicId: ID!) {
slotReleased(clinicId: $clinicId) { id startsAt }
appointmentCancelled(clinicId: $clinicId) { id reason }
}go deeper
Know the shape: a subscription operation selects exactly one root field, and the selection set nested beneath that field can be as deep and as wide as you need.
Explain that the count is over the root fields collected after fragments are flattened, and that the single root field is what resolves to the source of events in the first place.
Show the design fork — two operations versus one root field emitting a union of event shapes — and be specific about the coupling and connection costs each choice buys.
Own the event-schema strategy: how many streams a client should hold open, how event payloads evolve without breaking every consumer, and whether a coarse feed field is worth the traffic it pushes to clients that do not want it.
## The rule The GraphQL specification carries a validation rule for subscription operations: the fields collected on a subscription's root selection set must number **exactly one**, and that one field must not be an introspection meta-field. It is a static rule, checked before execution, and it applies to subscription operations only — a query or a mutation may select as many root fields as it likes. So this is invalid: ```graphql subscription ClinicFeed($clinicId: ID!) { slotReleased(clinicId: $clinicId) { id startsAt } appointmentCancelled(clinicId: $clinicId) { id reason } } ``` The restriction is on **root** fields. Nesting below that single root field is unrestricted: `slotReleased` can return an object whose selection set is five levels deep with a dozen fields at each level, and the rule is untroubled. ## Why one Because the root field *is* the stream. Setting up a subscription resolves that one root field to a source of events, and every subsequent result the client receives is produced by executing the rest of the selection set against one event drawn from that source. One operation therefore maps to one source. Two root fields would mean two independent sources arriving on one operation, and the specification would then have to answer questions it deliberately never asks: what a result looks like when only one source has fired, whether the other field's key appears with a null, what happens when one source ends and the other does not, and how the two are ordered relative to each other. The introspection exclusion follows from the same idea. Introspection meta-fields describe a static schema; there is no source of events behind them, so `subscription { __typename }` has nothing to stream and is rejected rather than being allowed to hang. ## The count happens after fragments flatten The rule counts the fields *collected* on the root, which means fragment spreads are flattened first. So this is invalid too, for exactly the same reason as the two-field version: ```graphql subscription ClinicFeed($clinicId: ID!) { ...Releases ...Cancellations } fragment Releases on Subscription { slotReleased(clinicId: "c-3319") { id } } fragment Cancellations on Subscription { appointmentCancelled(clinicId: "c-3319") { id } } ``` Two spreads, two collected root fields, validation failure. This trips people who assume the check is a syntactic count of the braces they typed. It is not — it is a count over the flattened field set, which is why an interviewer who wants to separate 'has read the rule' from 'has understood the rule' asks the fragment version. ## The design fork when you genuinely need two streams A client on a hospital appointment graph that needs both slot releases and cancellation notices has two honest options, and choosing between them is schema design, not specification compliance. **Two subscription operations.** Each stream is its own operation with its own lifecycle. Consumers subscribe to only what they need, the two payload types evolve independently, and a consumer that cares about cancellations is not woken by every release. The cost is more concurrent subscriptions to manage — more server-side stream bookkeeping, more to re-establish after a disconnect, and a per-client footprint that grows with the number of event kinds. **One coarse root field carrying a union.** Declare something like `clinicEvents(clinicId: ID!)` whose type is a union of the event payloads, and let the client branch on the concrete type: ```graphql subscription ClinicFeed($clinicId: ID!) { clinicEvents(clinicId: $clinicId) { __typename ... on SlotReleased { slotId startsAt } ... on AppointmentCancelled { appointmentId reason } } } ``` One stream, one lifecycle, one reconnect to handle. The cost is coupling: every consumer receives every event kind on that field and discards most of them, adding an event variant is a change to a type that all of them read, and a chatty variant's volume is now paid for by consumers that do not want it. Neither is 'correct'. The coarse union is a good fit when the events genuinely belong to one logical feed that a single view renders; two operations are a better fit when the consumers are different screens with different appetites. What the specification settles is only that you must choose one of them — you cannot avoid the decision by stapling two root fields onto one operation.
- Does hiding the second root field inside a named fragment satisfy the rule?No. The rule counts the fields collected on the subscription root after fragment spreads have been flattened, so two spreads each contributing one root field produce two collected fields and fail validation. The check is static and happens before execution. Interviewers use this variant deliberately, because it separates candidates who memorised the rule from those who understood that it counts a flattened field set rather than the braces you typed.
- What does a single union-typed root field cost you compared with two separate subscription operations?Coupling. Every consumer of that field receives every event kind on the stream and discards what it does not want, and adding a variant changes a type all of them read. Two operations keep the concerns and the lifecycles separate, at the cost of more concurrent streams to establish, re-establish after a disconnect, and account for per client.
- Are introspection meta-fields allowed as the root field of a subscription?No. The single-root-field rule states that the one collected field must not be an introspection field, so `subscription { __typename }` is invalid. It follows from what the root field means: it must resolve to a source of events, and introspection describes a static schema rather than producing events, so there would be nothing for the operation to stream.
One subscription operation is one tap. You can shape what comes out of it as elaborately as you like, but you cannot make one tap draw from two pipes.
saying these in an interview costs you the question
- Says a subscription may select several root fields
- Thinks a fragment can smuggle in the extra root field
- Believes the limit is one field in the whole document
- Claims the restriction is a server library's, not the spec's
- Says two streams require two separate schemas
- Assumes a union payload has no consumer-coupling cost