In GraphQL execution, may sibling fields in one selection set resolve concurrently?
answer
- Order among siblings is not promised
- Permitted by the spec, never required
- One requirement makes it safe
- Side-effect free and idempotent
- Concurrency without parallelism still counts
basics
~20 sYes. Apart from a mutation's root fields, an executor may resolve the fields of a selection set in any order, including at the same time, because that resolution is required to be side-effect free, so order cannot change the response.
solid answer
~50 sThe GraphQL specification lets an executor work through the fields of a selection set **in whatever order it chooses, normally in parallel**. The licence rests on one requirement: resolution of every field except a mutation's top-level fields must be side-effect free and idempotent, so no interleaving can change the result. The only ordering rule is the serial execution of a mutation's root fields, which is a separate subject. Note that this is *permitted*, never *required* — a server may loop over the fields one at a time, may start every resolver and await pending values on a single-threaded runtime (concurrency without parallelism), or may dispatch fields to worker threads. So in a solar-array telemetry graph, `Site.inverters` and `Site.weatherStation` may both be in flight before either finishes, possibly on different threads. Never assume one sibling runs before another, and never let one leave state the other reads.
code
graphql · 8 linesquery SiteOverview {
site(id: "SITE-4471") {
inverters { serial statusCode }
weatherStation { irradianceWm2 ambientC }
activeAlarms { code raisedAt }
dailyYieldKwh
}
}go deeper
Recall the headline: except for a mutation's root fields, the executor may resolve fields in any order and usually overlaps them. Be ready to say why that is safe — field resolution is supposed to be side-effect free.
Explain the mechanics: the permission is on the grouped field set, it is permitted rather than required, and it shows up as sequential loops, awaited pending values on one thread, or real worker threads. Say which shape your runtime uses.
Show that you design for it. Point at the assumptions that break — sibling ordering, thread affinity, unbounded in-flight work — and describe how you spot them in review before a wide client document finds them in production.
Own the consequence: concurrent field resolution is the mechanism that turns one document into a burst of downstream load. Frame where the ceiling on that burst lives and who decides it, rather than leaving it as whatever the executor happens to do.
## What the specification actually permits Executing a GraphQL operation means walking a selection set and producing a value for every field that document selected. The specification describes that walk as an algorithm over a *grouped field set* — the fields to resolve for one object, keyed by their response key — and attaches a note that settles this whole topic: the executor may execute the entries of a grouped field set **in whatever order it chooses (normally in parallel)**. Two fields selected side by side under the same parent, which is what "sibling fields" means here, carry no ordering relationship at all. That licence rests on a single requirement, and it is worth stating precisely because candidates usually remember the permission and forget the condition: resolution of every field **other than a mutation's top-level fields** must be side-effect free and idempotent. If nothing a resolver does can be observed by another resolver, then no interleaving of those resolvers can produce a different response, and the executor is free to start all of them at once. The requirement is on the schema author, not on the executor. Nothing in a server checks it. A resolver that increments a shared counter is not made legal by being fast. The one ordering rule the specification does impose is that the **root fields of a mutation operation execute serially**, one completing before the next begins — that rule and how fields are grouped in the first place are their own subject. Note the boundary carefully: the rule covers only the mutation's *top-level* fields. The selection set of the payload a mutation field returns is an ordinary selection set, resolved like any other, so those fields are concurrent again. ## Permitted, never required: three shapes it takes in practice Nothing obliges a server to overlap anything, and the same schema behaves differently on different runtimes. * **Sequential.** The executor loops over the grouped field set and blocks on each field before moving to the next. Perfectly legal, and what many servers do for plainly synchronous resolvers. * **Pending values on one thread.** Each resolver returns a not-yet-complete value; the executor starts them all and then waits for each. On a single-threaded runtime this is *concurrency without parallelism* — two resolvers waiting on network calls overlap in time, but only one of them is ever executing code. Most of the surprises still apply, because the interleaving points are the awaits. * **A worker pool.** Fields are dispatched to threads and genuinely execute at the same instant on different cores. Here you additionally get true memory-visibility hazards, not just interleaving. From the client's side, all three produce the identical response. From the resolver author's side they are very different, and that gap is the practical content of this topic. ## What you may not assume **Ordering between siblings.** "The `permissions` field runs before the `readings` field, so I can stash the check result" is false on any of the three shapes above, and it is the single most common way this bites. **Thread affinity.** A parent field's resolver and its child's may run on different threads, and one resolver may begin on one thread and resume on another after awaiting. Anything hung off the current thread — a request id, a security principal, a tracing scope — is therefore not reliably in place. **A bounded amount of work.** A `SolarString` type with 37 fields, every one of them selected, over 214 strings on a site, is a very large number of simultaneously in-flight resolvers if each one calls a backend. Whether that is acceptable is a capacity decision somebody has to make on purpose. **That your tests exercise it.** A two-field test document rarely exposes an ordering assumption. The wide document a real client sends does. ## What stays deterministic anyway Concurrency changes *when* values are produced, not *what the response looks like*. The keys inside each object appear in the order the document selected them, not in completion order. Each list keeps the order of the source list, not the order its items finished. Every field error carries the path of the field it happened at, whichever thread produced it. So a response is reproducible even when the execution that built it never is. ## The review question this turns into When reading a resolver, ask one thing: **can any other resolver in this request observe what this one touches?** If the answer is no — it reads its parent value and arguments, calls a downstream, returns a value — concurrency is free and you never think about it again. If the answer is yes, you have either a bug that only appears under a wide selection or a coordination point that needs a deliberate, thread-safe design.
- If one field's resolver raises while its siblings are still in flight, are those siblings cancelled?The specification says nothing about it, so treat it as implementation-defined. Some executors cancel outstanding work, others let every started resolver run to completion and simply discard the values. That matters after, say, a deploy that changed a type's nullability: a field error that now nulls a whole object does not un-perform the downstream calls its siblings already issued, and any writes or cache fills they did have still happened.
- Does this apply to nested selection sets, or only to the root fields of an operation?Every selection set. The fields of a nested object are resolved by the same algorithm and carry the same freedom. The single exception is the top level of a mutation operation, whose root fields run serially; the selection set of the payload that mutation field returns is ordinary and concurrent again.
- Does the specification require an executor to run fields in parallel for performance?No. It permits any order and notes that parallel is normal, but a strictly sequential executor is fully conformant and produces an identical response. Performance is an implementation concern, so you cannot infer from the specification that your server overlaps anything — you have to know what your runtime does.
It is like handing four independent lookups to four clerks at once: you can only do that safely because none of them writes in the ledger the others are reading.
saying these in an interview costs you the question
- Says resolvers always run sequentially, top to bottom
- Assumes one sibling field primes state for the next
- Thinks the specification requires parallel execution
- Believes every resolver in a request shares one thread
- Forgets side-effect freedom is what makes it safe