In a GraphQL document, what is the difference between omitting an argument and passing null?
answer
- Three states, not two
- A default fills only one of them
- Providing a value beats declaring one
- Non-null rejects both empty cases
- Same rule inside the braces
basics
~10 sOmitting an argument means no value was provided, so the schema's declared default applies. Writing null provides a value, so the default is not substituted and the server receives an explicit null.
solid answer
~50 sThere are three states, not two. **Absent**: the argument name does not appear in the document; if the schema declares a default for it, coercion substitutes the default, and if there is no default and the type is nullable, the argument simply has no entry in the coerced arguments the server sees. **Explicitly null**: the argument appears holding the `null` literal, which is a *provided* value, so the default is not substituted and the server receives null. **Provided with a value**: anything else. On a Non-Null argument type, both an absent argument with no default and an explicit `null` are rejected during validation, before execution, as a request error rather than a resolver failure. The same three-state rule is applied field by field inside an input object literal, so `{}` and `{ status: null }` reach the server as different messages.
code
graphql · 9 lines# status omitted: the schema's declared default is substituted
mutation { changeEnrollment(enrollmentId: "ENR-90312") { status } }
# status provided as null: the default is not substituted
mutation { changeEnrollment(enrollmentId: "ENR-90312", status: null) { status } }
# same distinction one level down, inside an input object literal
mutation { changeEnrollment(enrollmentId: "ENR-90312", patch: {}) { status } }
mutation { changeEnrollment(enrollmentId: "ENR-90312", patch: { waitlistPosition: null }) { status } }go deeper
Recall that a written null is a value and a missing argument is silence, and that the schema's default only covers the silent case. That one sentence answers most screening versions of this question.
Be ready to walk the coercion order out loud: provided or not, default or not, nullable or not, and which combination produces no entry at all rather than an entry holding null.
Show that you have debugged this. Describe how a client that fills unset fields with null quietly turns a partial write into a clobber, and where you would catch it — in the document text, not in the resolver.
Own the guidance. Decide whether the platform's document builders may ever emit a null for an untouched field, and make that a reviewable client-side rule rather than something each service defends against on its own.
## There are three states, not two An argument in a GraphQL document is in exactly one of three states, and almost every bug in this area comes from collapsing the first two: 1. **Absent** — the argument name does not appear in the document. 2. **Explicitly null** — the argument appears, with the `null` literal as its value. 3. **Provided with a value** — anything else. JSON habits push people to treat 1 and 2 as the same, because a field set to `null` and a field left out of an object often mean the same thing in a REST payload. In GraphQL they are specified to differ, and the difference is exactly where default values live. ## The rule, in the order the server applies it For each argument the schema declares on the field being executed, coercion asks first whether the document provided a value at all: * **Not provided, and the argument definition declares a default** — the default value is used. This is the only path on which a default is ever consulted. * **Not provided, no default, and the argument type is nullable** — no entry is added at all. The server's coerced argument map does not contain the name. A resolver can therefore tell "the caller said nothing" from "the caller said null", because in one case the key is missing and in the other it is present holding null. * **Not provided, no default, and the argument type is Non-Null** — this never reaches coercion. The validation rule for required arguments rejects the document before execution. * **Provided as `null`, on a nullable type** — the value is null. The default is *not* substituted, because a value was provided; defaults fill absence, not emptiness. * **Provided as `null`, on a Non-Null type** — rejected during validation, because null is not a valid value for a non-null location. Note the asymmetry this creates: a Non-Null argument that declares a default may be omitted safely, but may never be written as `null`. Both rejection cases are **request errors**: the whole operation fails before any resolver runs, the response has no `data` key, and nothing partially executed. ## The same rule applies inside braces An input object literal is coerced field by field with the identical algorithm. Given an input object whose `note` field declares a default and whose `waitlistPosition` field is nullable with no default: * `preferences: {}` — neither field was provided, so `note` gets its default and `waitlistPosition` has no entry at all. * `preferences: { waitlistPosition: null }` — the field is present, holding null. So `{}` and `{ waitlistPosition: null }` are two different messages to the server, and a schema is entitled to act differently on them. (Whether an update input *should* be designed to lean on that distinction is a schema-design decision, not a rule of the language.) ## A worked incident A course-enrolment service ran a `changeEnrollment` mutation whose input object had an optional `status` field. A client-side helper built the document by walking a form model and writing every field it knew about, filling anything the user had not touched with `null` rather than leaving it out. At an ordinary load that was invisible, because the form was usually complete. Then a network blip caused the client's retry logic to resend an in-flight mutation, and the duplicate write landed with a half-populated form model: the retry carried `{ status: null }` where the first attempt had carried `{ status: ENROLLED }`. The server, correctly, read the second document as "set status to null" rather than as "leave status alone", and the enrolment that had just been confirmed was blanked. Nothing here was a server bug and nothing was a spec ambiguity. The document said null, and null was honoured. The fix was on the writing side: omit the key instead of nulling it, which is one line in a document builder and is the difference between "I am not talking about status" and "set status to nothing". ## What this means when you read a document When you review a GraphQL document, read a written `null` as an instruction and a missing argument as silence. Ask which of the two the schema's default was written for. A default exists to give the silent case a meaning; if a caller is never silent because their tooling fills every slot with null, that default is dead code and the schema's author will be surprised by what the field does. ## Reading it from the server side Because the two cases differ in whether the key exists rather than in what it holds, checking the value alone cannot tell them apart. A resolver that wants to distinguish them has to ask whether the argument was present, not merely whether it is null — and if it only ever asks "is it null?", the distinction the language went to the trouble of preserving is thrown away at the last step.
- What happens if an argument whose type is Non-Null is written with the `null` literal?The request fails during validation, before execution: null is not a valid value for a non-null location. The response carries errors and no `data` key and no resolver runs. Note the asymmetry it creates — a Non-Null argument that declares a default may be omitted safely, because the default fills the absence, but it may never be written as `null`.
- Do the same rules apply to the fields inside an input object literal?Yes, field by field. An omitted field with a declared default gets the default; an omitted field with no default and a nullable type gets no entry at all; an omitted field whose type is non-null with no default fails validation; and a field written as `null` is present holding null. That is why `{}` and `{ status: null }` are different messages.
- If an argument declares a default and the caller writes `null`, which one wins?The explicit null. A default value is consulted on exactly one path — when no value was provided at all — so any provided value, including null, prevents substitution. Reading a default as a fallback for emptiness rather than for absence is the single most common mistake in this area.
A declared default is the spare key under the mat: it is used only on the days you turn up empty-handed. Writing null is turning up holding a key that opens nothing, and the spare stays where it is.
saying these in an interview costs you the question
- Says null and omitted mean the same thing
- Claims a declared default replaces an explicit null
- Believes null satisfies a Non-Null argument
- Fills every unset field with null when building a document
- Expects an omitted argument to arrive as a key
- Thinks a missing required argument fails inside the resolver