How does a default value on a GraphQL field argument differ from making that argument Non-Null?
answer
- Two knobs, two different questions
- Absence is not the same as null
- A default only covers the absent case
- Non-Null forbids the null literal outright
- Optional to write, yet never null
basics
~20 sA default value decides what the server sees when the caller omits the argument. Non-Null decides whether null is an accepted value at all. They are independent: an argument can have a default, be Non-Null, both, or neither.
solid answer
~50 sThe two knobs answer different questions. A **default value** applies only when the argument is *not provided* - the server then behaves as if the default literal had been written. **Non-Null** (`!`) says the argument's type admits no null, so a document may not pass an explicit null literal for it. Combining them gives four forms. `limit: Int` is optional and reaches the server as null when omitted. `limit: Int = 47` is optional and reaches the server as 47 when omitted. `limit: Int!` must be written in every document that selects the field, or validation rejects it. `limit: Int! = 47` may be omitted - the default covers it - but may never be given null. The classic trap: with `limit: Int = 47`, writing `limit: null` explicitly does **not** fall back to 47; null was provided, so the server sees null.
code
graphql · 13 linesenum SortOrder {
POSTED_DESC
POSTED_ASC
}
type Account {
transactions(
minAmountMinor: Int
limit: Int = 47
currency: String!
order: SortOrder! = POSTED_DESC
): [Transaction!]!
}go deeper
Recall the shorthand: the default covers the case where the caller says nothing, and the exclamation mark says null is not allowed. Be able to read limit: Int = 47 aloud and say what a caller who omits it gets.
Explain the three caller states - absent, explicit null, real value - and why a default only rescues the first. Then walk all four combinations of default and Non-Null, including Int! = 47, without hesitating.
Show that defaults are contract. Changing one silently changes results for every document that omits the argument, and tightening an argument to Non-Null invalidates whole documents on the next request, not just that one field.
Own the policy. Decide which knobs get standard settings baked in versus left to the caller, and set a rule for how argument nullability and defaults may move once published, so behaviour never changes under callers unannounced.
## Two independent questions Every field argument answers two questions that people routinely collapse into one: 1. **What happens when the caller does not mention this argument at all?** That is what a default value decides. 2. **Is null an acceptable value for this argument?** That is what the Non-Null modifier decides. Because they are independent, all four combinations are legal SDL, and being able to recite what each one means to a caller is the whole question: ```graphql type Account { transactions( minAmountMinor: Int # optional; omitted -> null limit: Int = 47 # optional; omitted -> 47 currency: String! # required in every document order: SortOrder! = POSTED_DESC # optional; omitted -> POSTED_DESC; never null ): [Transaction!]! } ``` ## "Not provided" is a third state, distinct from null This is where candidates fall down. A nullable argument has **three** states a caller can put it in, not two: * **Not provided.** The argument name does not appear in the selection. If the argument declaration has a default value, the default is used. If it has none, the argument counts as absent, which a server surfaces as null. * **Provided as null.** The caller wrote `limit: null`. The value *was* provided; it is null. **A default value does not apply here.** The default is a fallback for absence, not a null-coalescing rule. * **Provided with a value.** The ordinary case. So with `limit: Int = 47`, `transactions(limit: null)` sends null and the 47 never comes into play. That distinction is not pedantry: it is often how a schema lets a caller say "explicitly no limit" as opposed to "whatever you normally do". The same three-state logic reaches through variables. If a document writes `limit: $limit` and the request omits `$limit` entirely, the argument counts as not provided and its default applies. If the request supplies `$limit` with the value null, null was provided and the default does not apply. A caller who builds request variables by serialising an object with an optional field needs to know which of those two their serialisation produces. ## What Non-Null actually forbids `!` on an argument type says the type does not admit null. Two consequences: * A document may not write an explicit null literal for it, and may not pass a nullable variable to it unless that variable has a default. Both are validation failures - the document is rejected before execution, so no partial result comes back. * Without a default, the argument must appear in every selection of that field. `currency: String!` means exactly that. The combination `order: SortOrder! = POSTED_DESC` is the one worth being able to explain unprompted, because it reads like a contradiction and is not. It means **optional to write, but never null**. That is usually what you want for a knob with a sensible standard setting: the caller may leave it alone, and the code behind the field never has to handle an absent value. ## Defaults are contract, not implementation A default value written in SDL is part of the published schema. Callers, schema readers and generated client code can all see it, and a document written against it is silently relying on it. Two operational consequences follow. First, **changing a default changes behaviour for every existing document that omits the argument**, without invalidating a single one of them. Moving `limit: Int = 47` to `limit: Int = 12` breaks nobody's document and quietly truncates everybody's results. That is a change to review as carefully as a rename. Second, **tightening an argument's nullability is a breaking change**. Consider a bank statements graph where a deploy hardened `transactions(currency: String = "EUR")` into `transactions(currency: String!)` on the theory that the code behind the field "always needs a currency anyway". Every published document that had been relying on the default now fails validation on the very next request - the whole document is rejected, not just that field, so screens that also selected balances go blank too. Nothing in the change was caught by the server's own build, because the schema is perfectly valid; only the documents out in the world were invalidated. The safe order is the reverse: introduce the required behaviour as a nullable or defaulted argument, migrate callers, and only then consider tightening - if you tighten at all. The mirror-image rule is the reassuring one. **Adding a new argument that is nullable, or that is Non-Null with a default, is additive.** Every existing document omits it and keeps working. Adding a Non-Null argument with no default invalidates every existing document that selects the field. ## Constraints on the default literal itself A default value must be a **constant literal** of the argument's type - a scalar literal, an enum value, a list, or an input object literal. It may not reference a variable, and it may not reference another argument, so "default `page` to whatever `year` was" is not expressible in SDL. Anything conditional has to live behind the field, not in the declaration.
- Can an argument's default value reference a variable or another argument?No. A default value must be a constant literal of the argument's type - a scalar or enum literal, or a list or input object built from literals. It cannot mention a variable, and it cannot be computed from a sibling argument. Anything conditional has to be handled by the code behind the field, which keeps the published schema a static, readable contract.
- Is adding a new argument to an existing field a breaking change?It depends on the two knobs. A nullable argument, or a Non-Null one with a default, is additive: every existing document simply omits it and keeps working. A Non-Null argument with no default invalidates every existing document that selects the field, because each one now fails validation for a missing required argument.
- What happens if a document passes a nullable variable to a Non-Null argument?Validation rejects the document, unless that variable declaration itself carries a default value - which guarantees a non-null value can always be supplied. The check is static: it compares the variable's declared type against the argument's declared type before execution, rather than waiting to see what value the request actually carries.
A default is what a form fills in when you leave a box blank. Writing "none" in the box is not the same as leaving it blank - and Non-Null is the rule that the box may not contain the word "none" at all.
saying these in an interview costs you the question
- Thinks an explicit null falls back to the default
- Says Non-Null and defaulted are mutually exclusive
- Treats a defaulted argument as required
- Calls changing a default a safe, invisible tweak
- Believes a missing argument fails at execution, not validation
- Writes a variable expression as a default value