skip to content

In a GraphQL schema, what does a default value on an input object field do when the client omits it?

level: juniorimportance: should knowfreq 48%

answer

  1. Filled in before the resolver runs
  2. Changes whether the caller must send it
  3. Required means Non-Null and no default
  4. Explicit null bypasses the default
  5. Absence is erased by coercion

basics

~10 s

The server applies the declared default during input coercion, before any resolver runs, so the resolver receives the default value and cannot tell that caller apart from one that sent the same value explicitly.

solid answer

~50 s

A default declared in SDL, such as `tipCents: Int! = 0` on a restaurant ordering graph's `PlaceOrderInput`, is applied during input coercion, before execution. Omit the field and the coerced input the resolver sees carries the default; send the field and the caller's value wins. Two consequences matter. First, a default makes a field optional to send even when its type is Non-Null: an input field is required only when it is Non-Null **and** has no default, so `Int! = 0` may be omitted but may never be sent as `null`. Second, the default erases absence — after coercion nothing downstream can distinguish "omitted" from "sent with exactly the default value", which is why a default on a partial-update field is usually a mistake. Defaults in the type system must be constant literals; they cannot reference a variable.

code

graphql · 6 lines
graphql
input PlaceOrderInput {
  tableNumber: Int!
  tipCents: Int! = 0
  dietaryNote: String
  channel: OrderChannel = DINE_IN
}

go deeper

for a junior

Be ready to say what the resolver actually receives when a defaulted field is left out, and to read tipCents: Int! = 0 correctly: Non-Null, defaulted, and therefore optional for the caller to send.

for a middle

Explain the coercion rule precisely — a default replaces absence and never replaces an explicit null — and why the test for a required input field is Non-Null AND no default rather than Non-Null alone.

for a senior

Show that you treat a default as published contract: changing one silently changes behaviour for every caller that omits the field, so it needs the same review, rollout and usage evidence as a type change.

for a principal

Own the house rule about where defaults are allowed at all — creation inputs yes, partial-update inputs no — so absence keeps its meaning across the whole schema instead of being re-decided input by input.

## Where a default value lives A default value is part of the **type system**, written in SDL next to an input object field or an argument: ```graphql input PlaceOrderInput { tableNumber: Int! tipCents: Int! = 0 dietaryNote: String channel: OrderChannel = DINE_IN } ``` On a restaurant ordering graph, `PlaceOrderInput` says two things at once here. `tipCents` and `channel` may be left out by the caller, and if they are, the server itself supplies `0` and `DINE_IN`. This is *not* the same construct as a default written on a variable definition inside an executable document; that one belongs to the client's document, this one belongs to the published schema and applies to every caller that ever omits the field. ## When the default is applied Defaults are applied during **input coercion**, which happens after the document is parsed and validated and before any resolver runs. Coercion walks the values the client supplied against the declared input types and produces the coerced input the server hands to your code. The rule it follows for each field is short: 1. The field was **provided** — use the client's value, coerced to the field's type. The default is ignored, even if the client's value happens to equal it. 2. The field was **not provided** and a default exists — use the default. 3. The field was **not provided** and no default exists — the field is simply **absent** from the coerced input. It is not null; it is not there. 4. The field was provided as **explicit null**. If the field's type is nullable, the coerced value is null and the default is *not* consulted. If the field's type is Non-Null, the request is invalid regardless of any default. Point 4 is the one candidates get wrong most often. A default is a substitute for **absence**, never a substitute for null. Writing `dietaryNote: String = "none"` does not stop a caller from sending `null`; it only decides what happens when the caller says nothing at all. ## Required is not the same as Non-Null The spec's test for whether a caller *must* supply an input field is: the field's type is Non-Null **and** it has no default value. Add a default and a Non-Null field becomes optional to send. That makes `tipCents: Int! = 0` a genuinely useful shape. The caller may omit it, and when they do, the value the resolver sees is `0` — never null, because the type forbids null. A caller who tries to send `"tipCents": null` gets an invalid request. So the field is optional to *send* and guaranteed to *arrive*, which is exactly what you want for a numeric knob with a sane zero. Contrast that with `tipCents: Int = 0`. Now the field is nullable, so a caller can send an explicit null and the resolver sees null. The default protects you from omission, not from the caller. ## Defaults must be constants A default value in the type system is a literal — a scalar, an enum value, a list or an input object literal built out of literals. It cannot reference a variable, because variables are declared in an executable document and nothing in the schema knows anything about a particular request. There is also no expression evaluation: no "today's date", no "the current user's default table". If the value has to be computed per request, that computation belongs in your resolver, and the field stays undefaulted so you can tell whether the caller expressed a preference. ## The design consequence: a default erases absence Once coercion has run, nothing downstream can distinguish "the caller omitted this field" from "the caller sent exactly the default value". Both produce the same coerced input. That is fine, and often desirable, for a **creation** input: nobody cares whether a new order arrived with `channel` unset or with `DINE_IN` typed out. It is a bug on a **partial update** input. If `UpdateReservationInput.dietaryNote` carries a default, then every update that does not mention the note now carries a value for the note, and "leave that field alone" has become unexpressible. The general house rule is worth stating out loud in a schema review: defaults on creation inputs, never on update inputs. ## Defaults are part of the published contract Defaults are visible through introspection — the type-system introspection surface exposes each input field's and argument's default alongside its type — so tooling, documentation and typed client generators all read them, and a generator will normally mark a defaulted field as optional in the type it emits. The flip side is that **changing** a default is a behavioural change with no loud failure. If a 4-person platform team flips `channel`'s default from `DINE_IN` to `TAKEAWAY`, every client that omits the field keeps sending byte-identical documents and quietly starts placing takeaway orders. No validation error, no compile error, no client release. A default deserves the same review attention as a field's type: it is the promise you make to callers who say nothing.

  • Does a default on a nullable input field apply when the caller sends an explicit null?
    No. Coercion consults a default only when the field was not provided at all. An explicit `null` on a nullable field coerces to null and the default is ignored — that is the one case where a caller can defeat the default. On a Non-Null field the explicit null is simply invalid, default or not.
  • Can an input field's default value refer to a variable or be computed per request?
    No. A default in the type system must be a constant literal — a scalar, an enum value, or a list or input object literal built from constants. Variables are declared in an executable document and are not in scope where the schema is defined. Anything that has to be computed per request belongs in the resolver, and the field stays undefaulted so you can still tell whether the caller expressed a preference.
  • What happens when a 4-person platform team changes an existing field's default from DINE_IN to TAKEAWAY?
    Nothing fails, which is the danger. Every caller that omits the field keeps sending byte-identical documents and quietly starts getting different behaviour — no validation error, no build error, no client release. A default is part of the published contract, so changing one deserves the same review and rollout thinking as changing a field's type.

A default is not a fallback the kitchen reaches for when it notices something missing; it is written onto the order slip before the slip ever leaves the front of house.

saying these in an interview costs you the question

  • Says the resolver sees null when a defaulted field is omitted
  • Thinks a Non-Null input field must always be sent
  • Believes sending explicit null triggers the default
  • Puts a default on a partial-update field and loses absence
  • Assumes changing a default is an invisible, safe edit
  • Tries to write a variable or expression as a default

context