skip to content

In a GraphQL schema, what may a client send for a nullable input field that a Non-Null one forbids?

level: seniorimportance: should knowfreq 46%

answer

  1. On input, the obligation runs the other way
  2. Count what the caller can actually do
  3. Absent and explicitly null are not the same
  4. A default rescues omission, never null
  5. Rejected during validation, before any resolver

basics

~20 s

A nullable input field accepts three distinguishable moves: omit it, send an explicit null, or send a value. A Non-Null one accepts only the last — null is never valid there, and omission fails validation unless a default supplies the value.

solid answer

~50 s

On an input position — a field argument, an input object field or a variable — Non-Null is a requirement on the caller rather than a promise from the server. A nullable input field has **three** client-observable states: absent, present-and-null, and present-with-a-value. During input coercion those first two stay distinct — an omitted field is simply not present in the coerced input, while an explicitly null one is present carrying null. Marking the field Non-Null collapses that to a single state: null is not among the type's values, and omission is a validation failure unless a default value is declared, in which case the default fills in but null still cannot be sent. That is checked before execution begins, so the whole operation is rejected rather than one field failing. The three-state distinction is what makes a clear-this-value operation expressible at all.

code

graphql · 13 lines
graphql
input JobPostingPatch {
  title: String
  salaryMax: Int
  requiredSkillIds: [ID!]
}

type Mutation {
  updateJobPosting(id: ID!, patch: JobPostingPatch!): JobPosting
}

type Query {
  jobPostings(skills: [String!], first: Int! = 25): [JobPosting!]!
}

go deeper

for a junior

Know the basic direction: on an argument or input field, ! means the caller has to supply a value. Be able to say that a nullable argument may simply be left out of the operation.

for a middle

Explain the three states of a nullable input position — absent, explicitly null, present with a value — and that coercion keeps absent and null apart. Know that a default makes omission legal without ever making null legal.

for a senior

Show the design consequence with an example: a Non-Null input field cannot express clearing a value, which is why patch-style inputs stay nullable. Be clear that this is checked before execution, so the whole operation is rejected rather than one field failing.

for a principal

Own the ambiguity these states create across an API. Decide and document what absent, null and an empty list each mean for filters and patches, verify the server and generated clients actually preserve the distinction, and prefer removing a state to leaving callers guessing.

## The modifier binds the caller here, not the server The same `!` that promises a caller a non-null value on an output field does the opposite job on an input position. On a field argument, an input object field or a variable definition, Non-Null is a **requirement the client must satisfy**, and it is enforced during validation and input coercion — before a single resolver runs. That timing matters. A violated Non-Null input is not a field that failed while executing; it is an operation that never executed. The request is rejected as invalid, so nothing partial comes back and no side effect fires. ## Three states, and the one that disappears A nullable input field has three states a client can put it in, and they are genuinely distinguishable: 1. **Absent** — the field does not appear in the argument or input object at all. 2. **Explicitly null** — the field appears, carrying the literal `null` (directly, or through a variable whose supplied value is null). 3. **Present with a value.** The specification's input coercion keeps the first two apart: a field that is not provided and has no default is simply *not present* in the coerced input value, while a field provided as null *is* present with the value null. A server can therefore tell "the caller said nothing about this" from "the caller said: nothing". Mark that field Non-Null and state 2 vanishes outright — null is not among the values of a Non-Null type, so it can never be sent — and state 1 becomes a validation error unless the field or argument declares a default value. With a default, `first: Int! = 25` may be omitted (the default supplies the value) but may still never be given null. The result is that a Non-Null input field is effectively single-state from the caller's point of view: supply a value. ## Why anyone cares: clearing a value Take a job-board graph with an update mutation `updateJobPosting(id: ID!, patch: JobPostingPatch!)`, where `JobPostingPatch` declares `title: String`, `salaryMax: Int` and `requiredSkillIds: [ID!]`. A posting that has been advertising a ceiling of 96,400 now wants no published maximum. Because `salaryMax` is nullable, the client can send `"salaryMax": null` and the server can distinguish that from a patch that never mentioned `salaryMax`, applying it as "clear the value" while leaving unmentioned fields alone. Declare it `salaryMax: Int!` instead and that operation becomes inexpressible: the caller can raise or lower the ceiling but can never remove it, because the only way to say "no value" — null — is forbidden by the type. Two honesty points belong in the answer here. First, **"absent means leave it, null means clear it" is a convention, not something the specification defines.** What the specification guarantees is only that the two arrive distinguishably; what they *mean* is the server's decision and must be documented, because a caller cannot read it off the schema. Second, some servers and generated clients flatten absent and null into the same in-memory representation, at which point the distinction the specification preserved on the wire is lost inside the implementation — which is exactly the kind of thing to check before designing a patch mutation around it. ## Lists multiply the states The same reasoning applies one layer deeper on a list-typed input. An argument declared `skills: [String!]` on a `jobPostings` field has four caller-visible states: absent (no skill filter at all), explicit null (which the server may treat the same as absent, or not — again, its choice), an empty list `[]` (which reads naturally as "match nothing", or as "no filter", depending on whose code you read), and a populated list. This is precisely where filter arguments acquire surprising behaviour, and the fix is rarely a type change — it is deciding and documenting what each state means, and, where the ambiguity is not worth carrying, removing a state by making the argument Non-Null with a default. ## What a strong answer sounds like Say the direction of obligation first — on input, `!` binds the caller. Then count the states: three when nullable, one when Non-Null, with a default turning omission back into a legal move without ever admitting null. Then name where it is checked: validation and coercion, before execution, so the whole operation is rejected. Then give the concrete consequence: a Non-Null input field cannot express "clear this", which is why patch-style inputs keep their fields nullable. Finish by flagging the convention boundary — the specification separates absent from null, but the meaning you assign to each is yours to define and document.

  • An argument is declared `first: Int! = 25`. May a client omit it, and may it send null?
    It may omit it — the declared default supplies the value, so a Non-Null argument with a default is optional to provide. It may never send null: the default is only consulted when no value is provided, and null is not among a Non-Null type's values, so an explicit null fails validation regardless of the default. Defaults make omission legal; they never make null legal.
  • What does sending an explicit null for a nullable input field that declares a default value produce?
    Null, not the default. The default value is used only when the field is not provided at all; providing it explicitly as null means the coerced value is null. This trips people who assume a default is a fallback for any missing-looking input. If a caller must be able to reset a field to the declared default, that needs its own explicit representation — the type system offers none.
  • Is "absent means leave it, null means clear it" defined by the GraphQL specification?
    No. The specification guarantees only that the two are distinguishable after input coercion — an unprovided field with no default is absent from the coerced value, while an explicitly null one is present with null. The meaning is a widespread convention the server implements and must document, and some server and client implementations flatten the two together, so confirm the behaviour end to end before designing a patch mutation around it.

saying these in an interview costs you the question

  • Says omitting a field and sending null are the same thing
  • Thinks a Non-Null argument can still accept an explicit null
  • Believes a default value is applied when null is sent explicitly
  • Claims a bad input value fails only that one field
  • Says the specification defines null as meaning clear this value
  • Assumes a Non-Null input field can never be omitted

context