Skip to content
beginner Phase 1 · GraphQL Foundations and REST Comparison

Anatomy of a GraphQL Operation

Operation types, operation names, and the three top-level parts of a GraphQL document.

40m
1 problems
Topic Progress 0%

One Document, Three Operation Types

One Document, Three Operation Types

A GraphQL document is not the same thing as a query. The document is the text you send; it can hold several operations plus shared fragments, and the request picks which operation to run.

query GetCart($userId: ID!) {
  cart(userId: $userId) {
    itemCount
    items {
      ...LineItem
    }
  }
}

mutation AddItem($sku: String!, $qty: Int!) {
  addItem(sku: $sku, quantity: $qty) {
    ...LineItem
  }
}

subscription StockChanged($sku: String!) {
  stockChanged(sku: $sku) {
    ...LineItem
  }
}

fragment LineItem on CartItem {
  sku
  title
  unitPriceCents
  quantity
}

Three operation types, three behaviors at execution:

Type What execution does
query Reads. Fields at the same level may resolve in parallel.
mutation Writes. Top-level fields run one after another, in document order, so write order is predictable.
subscription Opens a long-lived stream; the same selection set runs once per event.

An operation can be anonymous (the query keyword with no name, or the bare { field } shorthand) only when the document contains exactly one operation. Two anonymous operations in one document are a validation error, not a style choice.

Names earn their keep in three places. With multiple operations in one document, the request carries operationName to pick one. Names show up in server logs, APM traces, and error reports, so GetCartFailed tells you more than query17. And persisted-query registries key operations by name and hash, see query variables for how the values arrive at execution.

Braces delimit every selection set: the operation's set, and each object-typed field's set nested inside it. The rule that trips people up is that an object type must always be followed by a selection set, while a scalar or enum field must never be followed by one.

Anatomy of a Selection Set

Anatomy of a Selection Set

A selection set is a list of entries, and there are only a few kinds of entry. Reading a query well means recognizing each one on sight.

query ProductPage($id: ID!, $withReviews: Boolean!) {
  product(id: $id) {
    id
    title
    small: thumbnail(size: 64)
    thumbnail(size: 256)
    ...PriceBlock
    reviews(first: 5) @include(if: $withReviews) {
      rating
      body
    }
  }
}

fragment PriceBlock on Product {
  priceCents
  currency
}

Line by line:

  • id is a plain field. The response key is the field name.
  • small: thumbnail(size: 64) is an aliased field. The response key becomes small, which lets one document request two shapes of the same field without a collision. The second thumbnail call appears under its own name.
  • ...PriceBlock is a fragment spread: the named fragment's selection set is pasted in at that position. Fragments are how shared shapes avoid being copy-pasted into every document.
  • reviews(...) @include(if: $withReviews) carries arguments and a directive. Arguments configure the field; @include/@skip decide at runtime whether the entry is part of the operation at all.
  • Nested braces under product repeat the pattern: object fields recurse, leaf fields stop.

Two practical consequences follow. First, the response object keys are aliases when present and field names otherwise, which is why aliases are also how you request the same field twice with different arguments. Second, fragments can carry a type condition (on CartItem), which is how a document selects fields that only exist on one variant of a union or interface; see inline fragments for that pattern.

Literals are legal in a document, but every value that varies by user or request belongs in a variable. A query with id: "42" baked in cannot be cached, cannot be reused, and ends up as a string-interpolation hole in the client.

Four Phases Between Text and Data

Four Phases Between Text and Data

The document you send passes through a fixed pipeline, and every error you will ever see belongs to exactly one stage of it.

Phase What happens Example failure
1. Parse Lexer and parser turn text into an AST query { user { is unclosed
2. Validate The AST is checked against the schema user { nmae } - no such field
3. Coerce variables Supplied variables are checked against declared types $id: ID! with no value
4. Execute Resolvers run for the selected fields a resolver throws or returns null for a non-null field

The order is the whole point. Validation compares the document to the schema before any resolver runs, so a typo in a field name can never reach the database. That is the practical payoff of putting types on the server: invalid operations are rejected structurally, not by whatever defensive code each resolver happens to carry.

Each stage also produces a different-shaped failure, which is why HTTP transport details can map them to status codes: parse and validation failures stop the request before execution, while execution failures travel inside a successful response alongside partial data.

Two validation rules are worth memorizing because they look like runtime bugs:

  1. Fields must be mergeable. Selecting the same field twice with different arguments or different sub-selections in one scope is a validation error, not a silent overwrite.
  2. Leaf and composite fields are exclusive. A scalar field with a sub-selection fails validation; an object field without one fails validation.

Interview questions in this area are almost always phrased as debugging stories: "the client sends a query and nothing reaches your resolver, what do you check?" Walk the four phases in order and the answer writes itself.

Practice Problems

0 / 1 solved
Write a Two-Operation Document with a Shared Fragment
Document structure

Write one GraphQL document for an account screen that (a) reads the current user with id, displayName, and email under the operation name AccountHeader, (b) updates the display name under the operation name RenameAccount taking a newDisplayName: String! variable, and (c) shares the id and displayName selection between both operations through a fragment. Show the operationName value the client must send to run the rename.

Example:

Input: Two operations and one shared shape in a single text document

Output: operationName: "RenameAccount" selects the mutation; both operations spread ...AccountIdentity on User

Because the document holds two operations, the request must name one. The fragment keeps id and displayName defined once instead of copied into both operations.

Optimal Solution — O(1) operation executed per request time, O(f) fragment selections reused space

Define the identity shape once as a fragment, reference it from both operations, and rely on operationName to disambiguate at request time.

query AccountHeader {
  me {
    ...AccountIdentity
    email
  }
}

mutation RenameAccount($newDisplayName: String!) {
  updateDisplayName(newDisplayName: $newDisplayName) {
    ...AccountIdentity
  }
}

fragment AccountIdentity on User {
  id
  displayName
}

Edge Cases:

  • Sending the document without operationName fails validation before execution
  • A fragment with the wrong type condition (on Account instead of User) fails validation

Quiz

1. A document contains two operations, and the request body does not specify operationName. What happens?

Question 1 options

2. In the selection set { small: thumbnail(size: 64) thumbnail(size: 256) }, what keys appear in the response object?

Question 2 options

3. The client requests user { nmae } and the server has no field named nmae. Which phase reports it?

Question 3 options

4. What is the effect of naming an operation instead of using the anonymous shorthand?

Question 4 options

Flashcards

Question

Document vs operation

Answer

A document is the text sent and can contain multiple operations plus fragments; an operation is one query, mutation, or subscription that actually executes.

Question

Anonymous operation rule

Answer

Allowed only when the document contains exactly one operation. Otherwise operationName is required.

Question

Response key rule

Answer

Response key = alias if present, otherwise field name. Aliases let one document request the same field twice with different arguments.

Question

The four phases of a request

Answer

Parse -> validate against schema -> coerce variables -> execute resolvers. Unknown fields die at validation, before any resolver runs.

Question

query vs mutation execution order

Answer

Query fields at one level may run in parallel; mutation top-level fields run serially in document order so writes stay predictable.

Revision Notes

Key Takeaways

  • 1. A document may hold several operations and shared fragments; operationName selects which one runs
  • 2. Selection sets contain fields, aliased fields, fragment spreads, and directive-bearing entries, and object fields recurse while leaf fields stop
  • 3. Parse, validation, variable coercion, and execution produce different failures, and validation happens before any resolver runs
  • 4. Mutation root fields execute serially while query root fields may run in parallel

Interview Tips

  • • Be able to state what happens when a query names a nonexistent field: rejected at validation, zero resolver calls, no database access
  • • Walk the pipeline (parse, validate, coerce, execute) when asked to classify an error; interviewers use that ordering to test whether you have actually run a server

Cheat Sheet

Operation anatomy cheat sheet

Document = operations + fragments. Operation = query | mutation | subscription.

Structure

query Name($var: Type!) { field(arg: $var) { sub ...Frag } }
fragment Frag on Type { leaf }

Keys: response key = alias ?? field name
Rules: one anonymous operation max; object field needs a selection set; leaf field must not have one; same response key must not carry conflicting args

Pipeline: parse -> validate (schema) -> coerce variables -> execute
Parallelism: query fields at a level may run in parallel; mutation root fields run serially.

Continue Learning