skip to content

Execution & Resolvers

How a validated document becomes a response: the execution algorithm the specification defines and the resolver contract it hands the implementer. This is where a clean schema meets messy backends.

part ofGraphQLoverview, primer and where to startread it →
on this pageshow

questions

29

When a GraphQL field returns an interface or union, how does the server decide the concrete object type?

level: juniorimportance: must knowfreq 62%

answer

  1. The schema declares shape, not instance
  2. Something must decide during execution
  3. One decision per resolved value
  4. It must name a possible type
  5. Nothing matches means a field error

basics

~10 s

The schema never says which one, so the executor asks the type system at run time, once per resolved value. That hook must name exactly one object type from the abstract type's possible types.

solid answer

~50 s

An interface or union field promises a value but not which object type it will be, and the executor cannot go any further without knowing: the fields that exist, the field resolvers that run and the fragments that contribute all depend on the concrete type. So value completion for an abstract type has an extra step in front of it. The specification models it as an internal type-resolution function the **type system** supplies: it receives the abstract type and the value the field resolver produced, and it must return an object type that is one of that abstract type's *possible types* — the union's listed members, or every object type declaring it implements the interface. The executor then collects and executes the sub-selection against that object type. It runs per resolved value, not per field, so a donations feed returning 4,312 contributions runs it 4,312 times. If nothing names a type, the specification says raise a field error.

code

graphql · 26 lines
graphql
interface Contribution {
  id: ID!
  amountMinor: Int!
}

type OneOffGift implements Contribution {
  id: ID!
  amountMinor: Int!
  giftAidClaimed: Boolean!
}

type RecurringPledge implements Contribution {
  id: ID!
  amountMinor: Int!
  monthlyOn: Int!
}

type GrantAward implements Contribution {
  id: ID!
  amountMinor: Int!
  awardingBody: String!
}

type Query {
  contributions(campaignId: ID!): [Contribution!]!
}

go deeper

for a junior

Recall that an interface or union field needs a run-time decision the schema cannot make, that it produces exactly one object type from the abstract type's possible types, and that the decision happens once for every resolved value.

for a middle

Be ready to explain why the decision must precede the sub-selection: fields, field resolvers and applicable fragments all depend on the concrete type. Know that failing to name one raises a field error rather than yielding an empty object.

for a senior

Show that you know the per-value cost and where it bites — large lists, deep documents, expensive discrimination — and that an unresolved value surfaces as a field error inside a 200 response that monitoring on status codes alone will never see.

for a principal

Own the policy question: whether abstract types are worth their run-time discrimination cost at all, where in the schema they earn it, and what the team's standard is for keeping resolution total as the possible-type set grows.

## What "abstract" costs the executor Interfaces and unions are GraphQL's two abstract types, and they are the only declared return types that do not answer the executor's question. A field declared `Money!` returns a `Money`. A field declared `Contribution`, where `Contribution` is an interface implemented by `OneOffGift`, `RecurringPledge` and `GrantAward`, returns one of those three — and the schema is deliberately silent about which. That silence stops execution dead at the sub-selection, because three separate things hang off the concrete type: * **Which fields exist.** An implementing type may declare fields the interface never did; a union member shares no fields with its siblings at all. * **Which field resolvers run.** Two implementers can resolve the same interface-declared field in completely different ways — `amountMinor` might be a stored column on one type and a computed sum on another. * **Which fragments contribute anything.** A fragment written on `RecurringPledge` contributes no fields to a value that is really a `GrantAward`. So the executor has to settle the type before it can do any of the work. ## The step the specification defines The specification models this as an internal function that the **type system definition must provide** — not something the document, the client or the schema text can answer. It takes the abstract type and the value that the field's resolver returned, and it must return an **object type** that is a *possible type* of that abstract type. "Possible types" is a precise term: * for a **union**, exactly the members listed in its definition; * for an **interface**, every object type in the schema that declares it implements that interface. With an object type in hand, the executor collects the sub-selection against it — dropping the fragments that do not apply — and executes it exactly as it would for a field that had been declared to return that object type all along. The resolved name is also what the response reports as the value's type when the document asks for it. ## Two consequences people miss **It runs once per value, not once per field.** A `contributions` field returning a list of 4,312 items resolves a type 4,312 times, and each of those resolutions can land on a different object type. In a 19-level-deep reporting document with an interface at several levels, the step runs at every level, for every value. **It runs even when the client asks for nothing type-specific.** A document that selects only interface-declared fields still forces the resolution, because the executor must know whose field resolvers to invoke. Skipping it "because no fragment needs it" would silently execute the wrong code. ## What the hook is allowed to return Exactly one object type from the possible set. Not the interface. Not the union. Not a list of candidates for the client to narrow. And not an object type outside the set — an interface's implementer that the union does not list is not a member of that union, and returning it would produce a response shape the client's document was never validated against. How the hook decides is left entirely to the server: the runtime class of the value, a discriminator column on the fetched row, or a plain type name carried on the record are all common. All three are implementation convenience, not specification. ## When nothing matches If the hook cannot name a possible type, the specification's answer is a **field error** raised at that field. The ordinary field-error consequences follow: that position in `data` becomes null, or the failure propagates outward if the field is non-null, an entry naming the field's path appears in the `errors` list, and the sibling fields of the document still resolve normally. It is a server-side execution failure inside an otherwise successful response envelope, not a transport-level failure and not a client validation error — which is exactly why it can sit unnoticed in a low-traffic branch for weeks. ## Worked shape A charity donations graph with a mixed feed makes the whole thing concrete: a `contributions` field declared to return `[Contribution!]!`, three implementing types with genuinely different fields, and a response in which item 1 was resolved to `RecurringPledge` and item 2 to `GrantAward`. Nothing in the SDL and nothing in the query decided that. A function running during execution, once per item, did.

  • If the document selects only fields declared on the interface, does the server still have to resolve the concrete type?
    Yes. The interface only declares that a field exists; each implementing object type supplies its own resolver for it. The executor cannot invoke a field resolver without knowing which object type it is executing against, so the resolution happens whether or not any fragment in the document cares about the answer.
  • How many times does a server resolve a type for a list of 4,312 contributions?
    Once per item, so 4,312 times. Type resolution is part of completing an individual value, not of completing the list; each element is resolved independently and different elements may land on different object types. That per-value cost is why an expensive resolution strategy over a large list shows up as latency.
  • Can the hook return an object type that is not among the abstract type's possible types?
    No. It must return one of the union's declared members, or one of the object types declaring they implement the interface. Anything else is treated as a failure to resolve, because the client's document was validated against the possible-type set and a foreign type would yield fields no fragment ever asked for.

A parcel labelled only "donation" cannot be routed; someone in the sorting room has to open it and stamp it "pledge" or "grant" before any of the type-specific handling can start.

saying these in an interview costs you the question

  • Says the client's inline fragments tell the server the type
  • Thinks the schema alone determines the concrete type
  • Believes the hook may return the interface or union itself
  • Says resolution runs once per field, not per value
  • Assumes an unresolvable value returns an empty object, not an error
  • Thinks resolution is skipped when only interface fields are selected

context

open as a page

In GraphQL execution, may sibling fields in one selection set resolve concurrently?

level: juniorimportance: must knowfreq 58%

basics

~20 s

Yes. Apart from a mutation's root fields, an executor may resolve the fields of a selection set in any order, including at the same time, because that resolution is required to be side-effect free, so order cannot change the response.

open as a page

What does a GraphQL server do for a schema field that has no resolver of its own?

level: juniorimportance: must knowfreq 62%

basics

~20 s

It uses a default field resolver: it reads a member of the same name off the parent value the field above already resolved - a property, getter or map key - and returns it unchanged. It performs no I/O.

open as a page

In GraphQL, are a mutation's root fields executed serially or in any order?

level: juniorimportance: must knowfreq 66%

basics

~20 s

Serially. GraphQL requires a mutation's top-level fields to run one after another in document order, so their side effects are ordered. A query's root fields are side-effect-free, so a server may run them in any order.

open as a page

What arguments does a GraphQL field resolver receive, and which of them does the specification define?

level: juniorimportance: must knowfreq 76%

basics

~20 s

Most servers hand a resolver four things: the parent value, the coerced arguments, a request-scoped context and an execution info object. Only the first two come from GraphQL's specification; context and info are an ecosystem convention.

open as a page

In GraphQL, what does a subscription's root field resolve to, unlike a query field?

level: juniorimportance: must knowfreq 62%

basics

~20 s

A subscription's root field resolves to a source event stream rather than a value. Subscribing runs that one field once to open the stream; every event on it then produces its own separate execution result for the client.

open as a page

In GraphQL execution, what does value completion do with the value a resolver returns?

level: juniorimportance: must knowfreq 54%

basics

~20 s

Value completion matches a resolved value to the field's declared type: lists are walked item by item, scalars and enums are serialized, object types have their sub-selection executed, and a Non-Null position holding null raises a field error.

open as a page

What belongs in a GraphQL resolver's request context, and why must it be built per request?

level: middleimportance: must knowfreq 66%

basics

~20 s

The context carries state belonging to one request: the authenticated principal, tenant, locale, a correlation id, a deadline and that request's batch loaders. Building it fresh per request is what keeps one caller's authorized data out of another caller's response.

open as a page

In a GraphQL subscription, what value does the selection set execute against per event?

level: middleimportance: must knowfreq 54%

basics

~20 s

The event itself. Each source event becomes the initial value for one ordinary execution of the selection set, so the subscription root field is resolved from that event by a normal field resolver, just as a query field is resolved from a root value.

open as a page

A GraphQL server's request-scoped context is corrupted only under load — how do concurrent resolvers cause that?

level: seniorimportance: must knowfreq 54%

basics

~20 s

Sibling fields may resolve on different threads, so any mutable object in the per-request context is written concurrently: a plain list or map, a lazily built client, a scratch field one resolver sets for another. Lost updates and stale reads follow.

open as a page

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

level: juniorimportance: should knowfreq 44%

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.

open as a page

In GraphQL, how does a type resolver on the abstract type differ from a per-type predicate?

level: middleimportance: should knowfreq 46%

basics

~20 s

One function on the interface or union inspects the value and names its object type. A per-type predicate instead lives on each object type and answers "is this value mine?", with the executor trying candidates until one says yes.

open as a page

In GraphQL execution, what happens when one selection set selects the same field twice?

level: middleimportance: should knowfreq 46%

basics

~20 s

The executor groups a selection set by response key before resolving anything, so both selections land in one group and the field resolves once. Their sub-selections are merged, so the response carries a single key.

open as a page

In GraphQL, why can a resolver's selection lookahead miss a selected field?

level: middleimportance: should knowfreq 41%

basics

~10 s

The document is not flat. A field can arrive through a fragment spread or an inline fragment, an alias renames its response key, and @skip or @include can remove it entirely.

open as a page

How does a GraphQL executor complete a list-typed field when one item cannot be completed?

level: middleimportance: should knowfreq 47%

basics

~20 s

The resolved value must be a collection, or the field errors. Each item is completed against the inner type under a path ending in its 0-indexed position, so a failing item raises at its own index and only that index.

open as a page

Why can adding a member to a GraphQL union break a field at run time, and how do you prevent it?

level: seniorimportance: should knowfreq 41%

basics

~20 s

Abstract type resolution lives in server code, not the schema: a new member passes validation while the resolver has no branch for it, so values of the new shape raise a field error in production. An exhaustive mapping fails the build instead.

open as a page

A GraphQL field declares arguments but has no resolver - what happens to those arguments?

level: seniorimportance: should knowfreq 50%

basics

~20 s

They are validated, defaulted and coerced normally, then handed to the default resolver, which ignores them and returns the parent value's whole same-named member. The filter or limit silently has no effect and no error is reported.

open as a page

In a GraphQL response, why can a selected field's key be absent rather than null?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Because the field was never collected. Collection runs before resolution and drops selections excluded by @skip or @include, or sitting in a fragment whose type condition misses the runtime type. An uncollected field gets no response key at all.

open as a page

When does GraphQL selection-set projection cost more than the over-fetch it saves?

level: seniorimportance: should knowfreq 36%

basics

~20 s

When the saved bytes are small and the mapping from selected fields to columns is hand-written. You trade a millisecond for many distinct statements, weaker plan reuse, and silent nulls when a field goes unmapped.

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

In GraphQL, what does the execution info argument give a resolver, and when is reading it justified?

level: seniorimportance: should knowfreq 44%

basics

~20 s

Execution info is the convention-only fourth resolver argument, describing where the field sits: parent type, declared return type, response path, schema and parsed operation. Read it for cross-cutting concerns such as per-field instrumentation; keep business logic out of it.

open as a page

In a GraphQL subscription, does a resolver error on one event end the response stream?

level: seniorimportance: should knowfreq 45%

basics

~20 s

No. Each event is a normal execution, so a field error is collected into that one event's errors entry and nulls propagate inside that one result. The stream keeps delivering later events. Only the underlying source stream ending or failing ends the subscription.

open as a page

How do you bound and isolate the concurrency that one GraphQL document's resolvers can create?

level: principalimportance: should knowfreq 40%

basics

~20 s

Put the bound where the scarce resource is: a cap on in-flight calls per downstream dependency, separate pools so one slow source cannot starve the others, and admission control on requests — not one global pool sized by guesswork.

open as a page

What is selection lookahead in a GraphQL resolver, and is it in the spec?

level: juniorimportance: nice to knowfreq 24%

basics

~20 s

Selection lookahead is a resolver reading the sub-fields the client selected beneath the field it is resolving, so it can fetch only those. The GraphQL specification never defines it; it is a server capability offered by convention.

open as a page

What fixes the key order of a GraphQL response map when resolvers finish out of order?

level: middleimportance: nice to knowfreq 26%

basics

~20 s

The document does. Keys appear in the order the fields were selected, with fragments flattened, no matter which resolver finished first. A serializer should preserve that order, but JSON defines no object ordering, so clients must not read by position.

open as a page

Does the GraphQL specification define default property-based field resolution?

level: middleimportance: nice to knowfreq 24%

basics

~20 s

No. The specification treats each field's resolver as an internal function the type system provides and never says how it computes a value. Reading a same-named property off the parent value is a universal implementation convention.

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

When a GraphQL subscription is unsubscribed, what must the server actually cancel?

level: seniorimportance: nice to knowfreq 24%

basics

~20 s

Unsubscribing cancels the response stream, and because that stream is only a mapping over the source event stream, the cancellation has to propagate: the source event stream and whatever produced it must be released too, along with any in-flight per-event execution.

open as a page

Why can renaming a GraphQL enum value break responses at execution rather than at validation?

level: seniorimportance: nice to knowfreq 21%

basics

~20 s

Validation only checks enum values sent in. Values sent out are checked during value completion, when the leaf is serialized: a stored value that maps to no declared enum value raises a field error, per object, long after every static check passed.

open as a page