skip to content

What is a variable in a GraphQL operation, and which types may it be declared as?

level: juniorimportance: must knowfreq 74%

answer

  1. Values travel beside the document
  2. Declared on the operation, not the field
  3. Only input types are allowed
  4. $name colon Type, optional constant default
  5. An object type is never a variable type

basics

~20 s

A GraphQL variable is a named, typed placeholder declared on the operation and filled from a separate values map sent with the request. Only input types are allowed: scalars, enums, input objects, and their list or non-null wrappers.

solid answer

~50 s

A variable is a typed placeholder declared in the operation definition - `query PlaylistTracks($id: ID!, $first: Int = 25)` - and referenced as `$name` anywhere an argument value is expected. Its value is never written into the document; it travels beside it in a separate map keyed by variable name, so one document text serves every call. A variable's declared type must be an **input type**: a scalar, an enum, an input object, or any list and non-null wrapping of those. Output types - object, interface and union types - are rejected when the document is validated, because a caller supplies values, not selections. A variable may carry a default written as a constant literal after `=`, and that default cannot reference another variable. Names are unique within an operation, every variable used must be declared, and every declared variable must be used.

code

graphql · 10 lines
graphql
query PlaylistTracks($id: ID!, $first: Int = 25, $markets: [Market!]) {
  playlist(id: $id) {
    name
    tracks(first: $first, markets: $markets) {
      id
      title
      durationMs
    }
  }
}

go deeper

for a junior

Be ready to write an operation with variables from memory: the parenthesised declarations after the operation name, $name at the use site, and the values map keyed without the dollar sign. Knowing that only input types are allowed is the second half of the answer.

for a middle

Explain why the input-type restriction exists rather than reciting it, and cover defaults precisely: constant literals only, legal on a non-null variable, and never a reference to another variable. Mention the declared-and-used rules that validation enforces.

for a senior

Show that you treat the document as a stable, reviewable artefact and the values map as the only moving part. Be ready to explain how fragments inherit variable requirements from the operations that spread them, and why a shape change needs a new document, not a new variable.

for a principal

Own the consequence for a shared graph: a fixed set of documents with parameterised values is what makes operations reviewable, testable and attributable to a team, whereas client-assembled query text turns every deploy into an unbounded surface nobody has seen.

## The split a variable creates A GraphQL request carries two things: a **document**, which is the text describing what to fetch, and a **variable values map**, which supplies the parameters. A variable is the joint between them. Declaring one tells the server "this operation takes a parameter of this type"; referencing `$name` inside the operation says "put its value here". The values themselves arrive as an ordinary keyed map alongside the document, most often JSON. That split matters because the document is *structure* and the values are *data*. A music catalogue client that shows one playlist screen has exactly one document, whatever playlist the user opened. The document is written once, checked once against the schema, and reused; only the map changes between calls. ## The grammar Variables are declared on the **operation**, in parentheses after the operation name, and nowhere else - not on a field, not on a fragment definition: ```graphql query PlaylistTracks($id: ID!, $first: Int = 25, $markets: [Market!]) { playlist(id: $id) { name tracks(first: $first, markets: $markets) { id title } } } ``` Each entry is `$name: Type` with an optional `= default`. The `$` is part of the reference syntax, not part of the name in the values map: the map key here is `"id"`, not `"$id"`. A variable can be used as a whole argument value, as a field of an input object literal, or as an item inside a list literal - anywhere the grammar admits an input value. ## Only input types The specification restricts a variable's declared type to an **input type**, and the rule is enforced during validation: - built-in and custom **scalars** (`Int`, `Float`, `String`, `Boolean`, `ID`, and anything the schema declares with `scalar`), - **enums**, - **input object types**, - and any **list** (`[T]`) or **non-null** (`T!`) wrapping of those. Declaring `$track: Track` where `Track` is an object type is invalid, and so is a variable typed as an interface or a union. The reason is direct: a caller sends *values*, and object, interface and union types describe things a server *returns*, whose shape depends on the selection set the document asked for. There is no way to write one down as an argument. This is also why a schema tends to grow a parallel family of input types - a `TrackFilterInput` beside a `Track` - rather than reusing output types for arguments. ## Defaults A variable definition may carry a default value: ```graphql query Chart($first: Int = 25, $market: Market! = GB) ``` Two details are worth holding on to. First, the default must be a **constant literal** - a number, a string, an enum name, a list or input object built from constants. It cannot reference another variable, because there is no ordering in which variables are resolved against each other. Second, a **non-null** variable may still carry a default. `$market: Market! = GB` is perfectly legal: the caller may omit `market` and get `GB`, but may not send `null` for it, because null is not a value of a non-null type. ## The document-level rules around variables Beyond the type restriction, a handful of validation rules govern variables in a document. Variable names are **unique within an operation**. Every variable a document *uses* must be **declared** by the operation that runs it, and every variable a document *declares* must be **used** - an unused declaration is an error, not dead weight the server tolerates. Because a named fragment cannot declare variables of its own, a fragment that references `$first` inherits the requirement: every operation that spreads that fragment, directly or transitively, must declare `$first`. There is also a compatibility rule between a variable's declared type and the argument position it lands in. A variable typed `Int!` may be used where `Int` is expected; a variable typed `Int` may **not** normally be used where `Int!` is expected, since a nullable variable could arrive as null in a position that forbids it. That is relaxed when a default rescues it - either the variable definition has a non-null default, or the argument itself declares one - because then no request can leave the position empty. ## What a variable is not A variable is not text substitution. The server does not paste the value into the document and re-parse it; the parsed document holds a *reference* to a variable, and the value is resolved separately when the request is executed. A consequence a beginner is often surprised by: a variable cannot stand in for a **field name**, an **alias**, a **type condition**, or a **directive name**. `$fieldName { ... }` is not a thing. Variables parameterise values only; if the client needs a different shape, it needs a different document, or an executable directive to include and skip parts of the one it has. A final practical note: variables never appear in the response. The response echoes the document's response keys and the data behind them, and knows nothing about how the arguments were supplied.

  • Can a variable be declared with an object type such as `Track`?
    No. A variable's declared type must be an input type - a scalar, an enum, an input object, or a list or non-null wrapping of those - and the document fails validation otherwise. Object, interface and union types describe what a server returns, and their shape depends on the selection set, so there is no way to write one down as a supplied value. Schemas therefore grow separate input types, such as a `TrackFilterInput` beside the `Track` object type.
  • May a nullable variable be used where a non-null argument is expected?
    Only when a default guarantees the position is never empty. The general rule is that a variable typed `Int` cannot be used where `Int!` is expected, because the caller could send null into a position that forbids it. The specification relaxes this when the variable definition carries a non-null default, or when the argument itself declares a default: in both cases an absent or defaulted value still yields something legal, so the usage is allowed.
  • Can a variable stand in for a field name or a type condition?
    No. Variables parameterise input *values* only - argument values, input object fields, list items. Field names, aliases, fragment type conditions and directive names are all part of the document's structure, fixed at parse time. If a client needs a different shape it sends a different document, or uses `@skip` and `@include` with a boolean variable to include or omit parts of the one it has.

Declaring variables turns a hardcoded request into a function with parameters: the body is written once, and each call passes a different argument list.

saying these in an interview costs you the question

  • Thinks the server interpolates variable values into the document text
  • Declares a variable with an output object or union type
  • Believes variables are declared on the field, not the operation
  • Assumes an undeclared $name is still filled from the values map
  • Thinks a variable default may reference another variable
  • Expects a variable to substitute a field name or type condition

context