skip to content

questions

3

What is the root value in GraphQL execution, and which resolvers receive it?

level: juniorimportance: should knowfreq 44%

answer

  1. Execution has to start somewhere
  2. The root type has no field above it
  3. The spec calls it the initial value
  4. Servers expose it as a rootValue option
  5. Depth one only, and usually null

basics

~20 s

The root value is the initial value the server hands to execution for one request. Only the root operation type's own fields see it, as their parent. Most servers pass nothing, so it arrives empty and root resolvers ignore it.

solid answer

~50 s

GraphQL execution is a walk down the selected fields: each field's resolver is called with the value the field above it produced. The fields of the root operation type have no field above them, so the specification seeds the walk with an **initial value**, passed into execution alongside the schema, the document and the coerced variables. Servers usually expose it as a `rootValue` option on their execute entry point. Every top-level field on `Query` receives that same object as its parent argument, and nothing deeper does - below depth one the parent is simply whatever the resolver above returned. In practice servers pass nothing, so it arrives as null, undefined or an empty map and top-level resolvers ignore it, pulling what they need from the per-request context instead. It is not the schema, not the field arguments and not the context, and it has no type in the SDL.

code

pseudocode · 16 lines
pseudocode
result = execute(
    schema:         pedigreeSchema,
    document:       parsedDocument,
    variableValues: { tag: "NL-4471" },
    initialValue:   null,            // the root value; nothing seeded here
    contextValue:   requestContext   // separate channel, reaches every field
)

// Query.animal is a depth-one field, so its parent IS the initial value
resolve Query.animal(parent, args, context, info):
    assert parent == null                       // nothing was seeded
    return context.pedigreeStore.findByTag(args.tag)

// Animal.sire is deeper: its parent is whatever Query.animal returned
resolve Animal.sire(parent, args, context, info):
    return context.pedigreeStore.findByTag(parent.sireTag)

go deeper

for a junior

Be ready to say that execution starts with an initial value the server passes in, that only the root operation type's own fields receive it as their parent, and that it is usually null in real servers.

for a middle

An interviewer expects you to walk the parent chain out loud: initial value into the root selection set, then each field's resolved value becoming the parent below it, and to separate the root value cleanly from arguments and context.

for a senior

Show that you know the root value is untyped and invisible to the schema, so anything smuggled through it becomes an undocumented dependency. Explain why request-scoped state goes in the context instead.

for a principal

Own the convention: a codebase where root resolvers read the parent argument has a hidden contract nobody can see from the SDL. Decide once, write it down, and keep the initial value empty except in tests and preloaded-aggregate cases.

## Execution has to start somewhere The GraphQL execution algorithm is defined recursively. To execute a selection set, the server runs each selected field against an *object type* and an *object value* - the value that the field above already produced. `Animal.sire` is resolved against the animal its parent field returned; `Herd.name` is resolved against the herd object. That chain explains every field in the document except the ones at the very top, because a query's root fields have no field above them to produce a parent. So the specification supplies one. `ExecuteRequest` takes an **initial value** in addition to the schema, the parsed document, the operation name and the coerced variable values, and passes it down as the object value for the root selection set. Ecosystem-wide that argument is called the **root value**, and most servers surface it as a `rootValue` (or similarly named) option on the function you call to execute a document. Specification vocabulary: initial value. Everyday vocabulary: root value. They are the same thing. ## Which resolvers actually see it Exactly one layer: the fields declared on the root operation type of the operation being executed. In a pedigree service whose `Query` type exposes `animal` and `herd`, both of those field resolvers receive the initial value as their parent argument, and they receive the *same* object - it is not copied or per-field. `Animal.sire`, two levels down, receives the animal returned by `Query.animal`. The root value does not travel down the tree on its own. If a deep field needs something from it, some resolver above must have returned it or embedded it in what it returned. This is the single most common misconception about the root value: candidates describe it as "the object every resolver gets". That description belongs to the request context, which is threaded to every field precisely because the parent chain is not a general-purpose channel. ## What it is not - **Not the context.** The context is the per-request bag of request-scoped state - the authenticated principal, loaders, tracing handles - and by convention it reaches every resolver in the tree. The root value reaches depth one. - **Not the arguments.** Arguments are declared on the field, sent by the client and coerced against the schema before the resolver runs. - **Not the root operation type.** The schema declares which object type is the query root; the initial value is a runtime value handed in beside it, and the two are unrelated at the value level. - **Not client-supplied.** A request body carries a document, variables and an operation name. There is no field in it that sets the root value; only the server chooses it, per request, at the moment it invokes execution. - **Not typed.** The root value has no declared type anywhere in the SDL. Introspection cannot see it, validation cannot check it and a schema reader cannot tell you whether one was passed. That invisibility is the main reason teams stop using it. ## Why it is usually empty A root resolver almost always has to fetch something - open a store, call a service, read a cache - and all of those handles live on the context because deeper resolvers need them too. Once the context exists, seeding a separate object that only the top layer can read buys nothing, so servers pass null and root resolvers never look at the parent argument. Reading a null parent in a top-level resolver is normal and correct, not a bug. ## Where it does earn its place When the request has *already* produced the whole object before execution starts, handing that object in as the initial value makes the top layer of the schema a plain projection of it: `Query.animal` becomes a field read rather than a fetch, and so does everything under it. That is the trivial-resolver shape - one loaded aggregate feeding a whole subtree - and it is also what makes the root value pleasant in tests, where you can execute a document against a hand-built object graph with no server wiring at all. ## What to say in an interview Name the three inputs execution receives beyond the document - schema, variables, initial value - say that the initial value is the parent of the root fields and of nothing else, distinguish it crisply from the context, and note that in real servers it is usually null. That answer shows you have read how execution actually starts rather than only how to write a resolver.

  • If the root value is normally null, where does a top-level resolver get the store or service handle it needs?
    From the per-request context the server builds before execution and threads to every field. That is the channel for request-scoped state - the caller's identity, connection handles, loaders, tracing - because unlike the initial value it is available at every depth, not only under the root operation type.
  • Does the root value have a type in the schema?
    No. The schema declares which object type is the query, mutation or subscription root, but the runtime value handed into execution is untyped: no SDL construct describes it, introspection cannot report it and validation never checks it. It is purely an implementation input to the execute call.
  • What parent value does a field three levels down receive?
    Whatever the field above it resolved to, after the executor has completed that value against the declared type - so an item of a list for a field under a list, and the concrete object for a field selected on an interface or union. The initial value never appears again after depth one.

Each resolver is handed the plate the resolver above it filled. Nobody sits above the root operation type, so the server puts down a starting plate itself - almost always an empty one.

saying these in an interview costs you the question

  • Says the root value is the per-request context object
  • Claims every resolver in the tree receives the root value
  • Confuses the root value with the root operation type
  • Thinks the client sends the root value in the request body
  • Believes the root value is declared somewhere in the SDL
  • Treats a null parent in a root resolver as a bug

context

open as a page

In a GraphQL server, why can a field that only reads its parent object still hit the database?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Trivial describes the code, not the work performed. Reading a property off the parent runs whatever the object does on access, so a lazily loaded association fetches on the read - once per parent object, invisible in the resolver map.

open as a page

What is a GraphQL root value genuinely useful for, and why do most servers leave it empty?

level: middleimportance: nice to knowfreq 21%

basics

~20 s

Seeding execution with an already-loaded aggregate makes the schema's top layer a plain projection of it, and it is a clean test seam. Most servers leave it empty: request-scoped state belongs in the context, which reaches every field.

open as a page