Multiple mutation fields in ONE operation execute strictly serially — one completes before the next starts — while multiple query fields have no such guarantee. Confirmed by running three incrementCounter mutations in a single request: they returned 1, 2, 3 in order, every time, exactly as the GraphQL spec requires.
graphqlqueriesmutationsfragmentsvariables
Knowledge Check
12 questions · pass at 70%
0/12
easymcq
1. What's the key structural difference between a typical REST API and a GraphQL API?
Interview Questions
2 questions
Cheatsheet
Download-ready reference
Cheatsheet
query { field1 field2 { nested } } → read data, shape MIRRORS the query
mutation { field1 field2 } → write data, side effects
VARIABLES: declared with a type, substituted separately —
query Greet($who: String!) { greeting(name: $who) }
variables: { "who": "Ada" }
⚠️ NEVER string-interpolate values directly into query text.
ALIASES: rename a field's response key, needed when requesting
the SAME field twice with different args in one operation:
{ a: greeting(name: "A") b: greeting(name: "B") }
FRAGMENTS: reusable field selections —
fragment UserFields on User { id name email }
...spread with ...UserFields inside a selection
⚠️ EXECUTION ORDER — confirmed by real execution:
MUTATIONS: top-level fields run STRICTLY SERIALLY
(spec-guaranteed — side effects need ordering)
QUERIES: top-level fields have NO ordering guarantee
(can run concurrently — reads don't need ordering,
and this is a real performance opportunity for
server implementations)
The beginner framing: instead of a REST API's many fixed-shape endpoints (/users/1, /users/1/posts), GraphQL exposes one endpoint and lets the client specify exactly which fields it needs, in exactly the shape it wants them, in the request itself.
The response shape mirrors the query shape exactly — no over-fetching (extra fields the client never asked for) and no under-fetching (needing a second request to get related data, the classic REST pain point that motivated GraphQL in the first place).
A nested query: parent resolvers run first, their result feeds child resolvers
step 1 / 4
query {
user(id: 1) {
name
posts { title }
}
}
Client
waiting
query sent
→
user resolver
active
runs FIRST — top-level field
→
name resolver
idle
not yet
→
posts resolver
idle
not yet
The query's top-level field, user(id: 1), resolves first. Its resolver typically hits a database or API to fetch the user.
Variables: parameterizing a query without string concatenation#
Confirmed by executing this exact query against a real schema: $who is declared with a type (String! — required, non-null) and substituted from a separate variables object sent alongside the query — never by string-interpolating a value directly into the query text, which would be both error-prone and, for user-supplied values, a real injection risk.
Aliases: requesting the same field twice, differently#
Confirmed by executing this: without aliases, requesting the same field twice with different arguments would collide in the response (both trying to occupy the same greeting key). Aliasing (a:, b:) lets each instance land under its own key.
query { user { ...UserFields }}fragment UserFields on User { id name email}
Confirmed by executing this: a fragment is a named, reusable set of fields for a given type, spread into a query with ...FragmentName. This matters most once a field selection is repeated across several queries/mutations in a real app — defining it once avoids the selections drifting out of sync with each other over time.
Mutations: the same syntax, a real execution-order guarantee#
Confirmed by executing this exact operation, repeatedly, with consistent results: multiple top-level mutation fields in a single operation execute strictly serially — the first completes before the second starts, and so on — which is why the counter increments predictably to 1, 2, 3 rather than in some unpredictable order. This is a genuine, spec-mandated difference from queries, where the GraphQL specification explicitly permits top-level fields to execute in any order (including in parallel) — mutations are ordered specifically because they cause side effects, and unordered side effects would make application behavior unpredictable in a way unordered reads generally don't.
Given that slowFetch is a query field (not a mutation) that takes noticeably different amounts of time to resolve depending on id, is first guaranteed to complete before second starts?
Solution
No — unlike mutations, the GraphQL specification does not guarantee any particular execution order for sibling query fields, and in practice, GraphQL execution engines commonly run independent top-level query fields concurrently precisely because there's no ordering requirement to respect. If second's underlying fetch happens to resolve faster than first's, there's nothing incorrect about that — the final response still correctly places each result under its own key (first, second), just potentially completed out of real-time order. This is the direct contrast to the mutation example above, where the serial guarantee is real and spec-mandated.
Build a minimal query-variable substitution function, to internalize what "variables" actually do underneath the syntax:
function substituteVariables(queryTemplate, variables) { // a GREATLY simplified stand-in for what a real GraphQL execution engine does: // replace $varName references with their provided value return queryTemplate.replace(/\$(\w+)/g, (match, varName) => { if (!(varName in variables)) throw new Error
Real GraphQL execution engines do something considerably more sophisticated (type-checking each variable against its declared type, handling variables used in multiple places, nested input types) — but this captures the essential idea: variables are substituted from a separate, structured source, never raw string concatenation.
The query-resolution visualizer above previews the resolver execution model — each field, whether top-level or nested, is backed by its own resolver function, a mechanism covered in full depth (including the problem that arises when nested resolvers each trigger their own database call) in N+1 Problem & DataLoader. The overall schema shape — what types, fields, and arguments even exist to be queried — is covered in Schema Design.
Beyond the security risk with untrusted input, this also defeats query caching/parsing optimizations that rely on the query text staying constant across calls with different values — variables keep the query text identical while only the values change.
2. Assuming query fields execute in the order they're written#
query { slowField fastField}# ❌ assuming slowField's result is guaranteed to be computed/available before fastField's
Only mutations have a serial execution guarantee — confirmed above. Query field execution order (and completion order) is intentionally unspecified.
3. Forgetting to alias when requesting the same field with different arguments#
query { greeting(name: "A") greeting(name: "B") # ❌ collides with the field above — same response key}
Without an alias, both invocations attempt to write to the same greeting key in the response — aliasing resolves the collision explicitly.
Always use variables for any value that isn't a hardcoded literal — never string-concatenate user input or dynamic values directly into query text.
Reach for fragments once a field selection is reused across more than one query/mutation, to keep them from drifting out of sync.
Don't rely on query field execution order for anything correctness-sensitive — if strict ordering of side effects matters, that's what mutations are for.
Alias deliberately whenever the same field is requested more than once in a single operation with different arguments.
Because query fields have no ordering requirement, a well-implemented GraphQL server can resolve independent top-level (and independent sibling) fields concurrently — this is a real, meaningful performance opportunity queries have that mutations, by design, don't.
Fragments have no runtime performance cost or benefit of their own — they're a purely textual/organizational convenience, expanded during query parsing before execution begins.