What arguments does a GraphQL field resolver receive, and which of them does the specification define?
answer
- Count the parameters, then ask who defined them
- Two are specified, two are convention
- Type and field name only pick the function
- Parent value plus already-coerced arguments
- Context and info appear nowhere in the spec
basics
~20 sMost 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.
solid answer
~50 sThe specification's `ResolveFieldValue` step takes four inputs -- the object type, the parent value, the field name and the coerced argument values -- but it uses the object type and field name only to **look up** the field's internal resolver, then calls that resolver with the parent value and the arguments. The resolver function itself is left entirely to the type system: the specification says nothing about its language, arity or extra parameters. Every mainstream server therefore adds two more by convention: a **context** carrying request-scoped state (authenticated principal, per-request loaders, correlation id) and an **execution info** value describing where the field sits (parent type, declared return type, response path, schema, operation). Arguments arrive already coerced to their declared types, so a resolver never reads raw literals or the variables map, and the parent value is whatever the parent field's resolver returned -- an internal object, not the response shape.
code
graphql · 10 linestype Query {
job(id: ID!): Job
}
type Job {
id: ID!
title: String!
company: Company!
applications(first: Int! = 25, status: ApplicationStatus): [Application!]!
}go deeper
Be able to name the four values a resolver typically receives and say which two the specification actually defines. Interviewers use this as a quick check that you have read past your own server's documentation.
Explain the mechanics: object type plus field name select the function, the parent value is the parent resolver's return value, and arguments are coerced before the call so defaults are already applied.
Show that you know which parts of the signature you can rely on across servers and which you cannot, and keep resolvers testable by depending on the parent value and arguments rather than on server-specific extras.
Own the convention as a boundary: decide what your platform guarantees resolver authors, so teams write against a stable contract instead of whatever the current server happens to expose in a fourth argument.
## The signature the specification defines GraphQL's execution algorithm resolves one field at a time. The step that produces a value is `ResolveFieldValue`, and it takes exactly four inputs: the **object type** the field was selected on, the **parent value** (the specification calls it the object value), the **field name**, and the **coerced argument values**. Its body is two sentences long: look up the internal resolver function that the type system provided for a field of that name on that object type, then call it, providing the parent value and the arguments. Read that twice, because the precision is the interview point. Four inputs go into the algorithm; **two** of them reach the resolver function. The object type and the field name are a lookup key -- they choose *which* resolver to ring -- and the resolver, once selected, is told only what object it is resolving against and what arguments the caller supplied. The specification then stops. It says the internal resolver is provided by the object type and defines nothing else about it: not its language, not whether it is synchronous, not its return type beyond "a value", and emphatically not any further parameters. Everything else in a real resolver's signature is a decision some server made and every other server copied. ## Arguments arrive already coerced The argument values a resolver receives are a map from the field's declared argument names to values already coerced to their declared input types. The client may have written a literal, or a variable, or nothing at all and let a declared default apply; by the time a resolver runs, all three look identical. A resolver never inspects the document text and never touches the variables map, and it cannot tell whether `first: 25` was typed into the document or supplied as a variable. (How literals and variables become coerced values is a subject of its own, and it finishes before this step begins.) One consequence catches people out. Because a declared default is filled in during coercion, a resolver cannot distinguish "the client omitted this argument" from "the client sent exactly the default value". If that difference matters to your domain -- a filter that must mean *unset* rather than *false* -- the argument has to be declared nullable with no default so that `null` and absent stay distinguishable. That is a schema-design decision; no resolver can recover the distinction afterwards. ## The parent value is an internal value The second thing a resolver gets is whatever the *parent field's resolver returned*. Not the response map the client will eventually see, and not an instance of the schema's object type. A job-board graph makes it concrete: `Query.job` returns a row loaded from a datastore -- an internal object carrying `id`, `company_id`, `posted_at`, `internal_req_code`, columns the schema never exposes. When `Job.company` resolves, *that row* is its parent value, and the resolver's job is to get from `company_id` to a company. So resolvers are written against internal shapes, and the schema's field names are the executor's concern rather than the resolver's. It is also why a resolver's return value is not the end of the story: the executor still has to complete that value against the field's declared type, which is a separate step with its own rules. ## The two arguments nobody specified Real servers pass more, and by overwhelming convention they pass the same two extras: - a **context** -- one object per request, threaded unchanged into every resolver, carrying request-scoped state: the authenticated principal, tenant, locale, a correlation id, a deadline, that request's batch loaders; - an **execution info** value -- where this field sits in the request: the parent type, the field's declared return type, the response path from the root, the schema, and the parsed operation with its variable values. Neither appears anywhere in the specification. That is not a gap to apologise for; it is exactly what "the internal resolver is provided by the type system" leaves room for. But it means the four-argument shape most engineers recite is three-quarters convention and one-quarter specification, and an interviewer who asks "which of those does the spec define?" is checking whether you know where the line falls. Note also that not every server exposes a four-argument function at all. The parent value may arrive as the method receiver, arguments as individually typed parameters, the context as an injected value. The *information* is the invariant; the arity is not. ## Specified versus conventional Specified: that a resolver is selected by object type plus field name; that it is called with the parent value and the coerced arguments; that the type system supplies it; that argument coercion happens before it runs. Conventional: the context argument, the execution info argument, everything inside them, and the parameter order itself. ## Traps worth pre-empting - **Calling the first parameter "the root" or "the query".** It is the parent value. It equals the initial value only for fields selected directly on the root operation type. - **Claiming the context is part of the specification.** This is the single most common overreach on this subject, and it is easy to check. - **Assuming a resolver can read the document.** Unless the server chooses to hand it over through the info argument, it cannot -- and building logic on that access is a decision, not a given.
- Why does the specification's ResolveFieldValue take the object type and field name if the resolver never sees them?They are the lookup key. A resolver belongs to one field of one object type, so the same field name on two object types is two different functions, and a field inherited from an interface still resolves per implementing type. Once the pair has selected the function, the function only needs the object it is resolving against and its arguments.
- A field declares `first: Int! = 25` and the client omits it. What does the resolver see, and what can it conclude?It sees `first: 25`. Declared defaults are applied while argument values are coerced, before the resolver runs, so omitted-with-a-default and explicitly-sent-the-default are indistinguishable inside the resolver. If your domain needs to tell them apart, declare the argument nullable with no default so `null` and absent remain distinct values.
- If a server exposes resolvers as class methods with typed parameters rather than a four-argument function, has it broken the convention?No. The specification fixes only that the resolver is selected by object type and field name and receives the parent value and coerced arguments. Delivering the parent value as the method receiver, arguments as typed parameters and the context as an injected value carries the same information in a different shape.
The object type and field name work like a switchboard's dial: they choose which line rings. The specification only says two things down that line -- the parent value and the arguments -- and every extra wire a server runs alongside it is the server's own.
saying these in an interview costs you the question
- Claims the specification defines a context argument
- Says the resolver parses the variables map itself
- Calls the first parameter the query root, not the parent
- Believes the parent value is the client-visible response shape
- Assumes every server passes the same four parameters
- Thinks arguments reach the resolver as raw strings