DeepFrontend
Practice ArenaBlogSign in
Go Pro
DeepFrontend

Interview-grade learning paths and hands-on practice for mid-to-senior engineers. Master internals, patterns, and system architecture with runnable code.

All systems operational

Core Courses

  • -JavaScript Internals
  • -React Reconciliation
  • -TypeScript rigor
  • -Next.js Caching
  • -Node.js Event Loop
  • -System Design
  • -Web Security
  • -CSS & Page Layouts

Explore

  • Learning Paths
  • Practice Arena
  • Pricing Options
  • HTML Sitemap

Resources

  • Privacy Policy
  • Terms of Service
  • Email Support

© 2026 DeepFrontend. All rights reserved.

Expert learning environments for web engineering teams.

Home/GraphQL/Queries & Mutations
beginner20 min read·Updated Jun 2025

Queries & Mutations

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)

Related topics

Fetch APISchema DesignPro

On this page

20m
Knowledge CheckInterview QuestionsCheatsheet

Concept#

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.

query {
  user(id: 1) {
    name
    posts {
      title
    }
  }
}
{ "data": { "user": { "name": "Ada", "posts": [{ "title": "Hello" }] } } }

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#

query Greet($who: String!) {
  greeting(name: $who)
}
{ "who": "Ada" }

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#

query {
  a: greeting(name: "A")
  b: greeting(name: "B")
}
{ "data": { "a": "Hello, A!", "b": "Hello, B!" } }

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.

Fragments: reusable field selections#

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#

mutation {
  first: incrementCounter
  second: incrementCounter
  third: incrementCounter
}
{ "data": { "first": 1, "second": 2, "third": 3 } }

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.


Try It#

Predict the outcome before checking the solution.

query {
  first: slowFetch(id: 1)
  second: slowFetch(id: 2)
}

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.


Implement It Yourself#

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.


Under the Hood#

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.


Common Mistakes#

1. String-interpolating values directly into a query instead of using variables#

const query = `query { user(id: "${userInput}") { name } }`; // ❌ injection risk, error-prone

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.


Best Practices#

  • 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.

Performance Tips#

  • 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.

(
`Missing variable: $${
varName
}`
);
return JSON.stringify(variables[varName]);
});
}
const template = `query { greeting(name: $who) }`;
console.log(substituteVariables(template, { who: "Ada" }));
// query { greeting(name: "Ada") }