In a GraphQL response, how does an absent data key differ from data present with the value null?
answer
- Three states, not two
- One side of the execution boundary each
- Your parser is hiding the difference
- Ask whether the key exists at all
- Absence is safe to retry, null is not
basics
~20 sAn absent data key means the operation never executed, so no resolver ran. A present data key whose value is null means execution did run and its result was discarded. Only the first guarantees nothing happened server-side.
solid answer
~40 sThey are two different states that most deserializers flatten into one. If `data` is **not a key in the response map at all**, the request failed before execution began — nothing was resolved and no side effect occurred. If `data` is a key whose value is `null`, execution *did* run; the server started resolving fields, hit a failure that left no usable result, and returned the null. The distinction matters most on mutations. A client that tests `if (body.data == null)` cannot tell the two apart, because a missing key and a null value both deserialize to the same nullish value in most languages, and if it then decides "the request failed, retry it", it will re-send a mutation whose writes already committed. Test **key presence**, not value nullness.
code
json · 5 lines{
"errors": [
{ "message": "Variable '$releaseId' of required type 'ID!' was not provided." }
]
}go deeper
Know that data can be missing from the response entirely, not just set to null, and that the two mean different things. Being able to say 'absent means it never ran' is enough at this level.
Explain both states and the boundary they sit either side of, and show you know your JSON layer collapses them. Be ready to name the concrete check — key containment on the parsed map — rather than gesturing at 'checking for null'.
Bring the consequence: a client that retries on a nullish data value will re-send mutations whose writes already committed. Talk about idempotency keys and read-back reconciliation as the recovery, and about auditing shared client wrappers for this exact test.
Own it as a client-contract problem rather than a bug. Decide whether every team writes its own retry logic against a raw body or whether one shared client encodes the distinction, and weigh the cost of that shared layer against duplicate writes appearing independently in several codebases.
## Two states, one nullish value The response envelope gives `data` three possible states, not two: 1. **Absent.** There is no `data` key in the response map. 2. **Present, null.** There is a `data` key and its value is the JSON value `null`. 3. **Present, a map.** The ordinary case, possibly with holes in it. Most people internalise states 2 and 3 and never notice state 1, because almost every JSON deserializer and almost every typed client models `data` as a single nullable field. A missing key and an explicit null both land as the same nullish value, and the distinction is gone before any application code runs. The envelope draws that distinction deliberately, and it carries real information. ## What each state tells you **`data` absent** means the operation was never executed. The server rejected the request before execution began — it could not parse the document, the document did not validate against the schema, or a variable value could not be coerced into its declared type. Because execution never started, no resolver ran, no backend was called, and no write happened. The `errors` list is present and describes why. **`data` present and null** means the opposite: execution began. The server resolved fields, and somewhere in that process the result became unusable at the root, so the whole result serialized as null rather than as a map. The work done up to that point still happened. Any resolver that already ran, ran; anything it wrote, it wrote. That is the whole point of the distinction. Absent means *nothing happened*. Null means *something happened and you are not being shown it*. ## Where this bites: the retry A four-person platform team runs a music catalogue graph. Their shared client wrapper has a plausible-looking helper: ``` if (response.data == null) { retryOnce() } ``` The intent was to survive request errors, which are usually transient in their environment. It works fine for queries. Then a `publishRelease` mutation starts returning a null `data` under load: the mutation resolver writes the release row, then the payload it returns contains a field the server cannot complete, and the failure reaches the root. The wrapper sees a nullish `data`, concludes the mutation did not happen, and sends it again. The second call writes a second row. Over nine days the team found 217 duplicate release rows, all traceable to a retry that fired against a response whose `data` was present-and-null rather than absent. The bug is not in the server and not in the schema; it is in a client that treated two envelope states as one. ## Checking it properly The check has to be on **key presence**, and how you spell that depends entirely on what your parser gives you: * A raw parsed map has a containment test — ask whether the key exists before asking what its value is. * Some languages can model the difference in the type system, with an optional wrapping a nullable, so an absent key and a null value are distinguishable values. * Many typed clients erase it. If yours models the response as one nullable field, you cannot recover the distinction downstream and must inspect the raw body before it is deserialized. The rule of thumb worth stating in an interview: **absence is safe to retry, null is not.** If `data` is absent, nothing executed, so re-sending is harmless — though re-sending an identical malformed or invalid document will simply fail identically, so the retry is pointless rather than dangerous. If `data` is present and null, execution ran and you must assume any side effects it could have had, it had. Recovering from that needs an idempotency key on the mutation or a read-back to check what actually landed, not a blind retry. ## Two things this distinction does not tell you It does not tell you *why*. The reason `data` came back null lives in the accompanying `errors` list, and the reason it was absent lives there too — the envelope only tells you which side of the execution boundary you are on. It also does not mean a null `data` implies a server crash. It is a defined, ordinary outcome of the execution rules, and a response carrying `"data": null` with a populated `errors` list is a perfectly conformant response, not a sign that anything is broken beyond the one failure being reported. ## The interview version Say the three states out loud, attach "execution never started" to absence and "execution ran" to null, and then give the consequence: a client that tests for nullness rather than key presence will retry operations that already took effect. That last sentence is what the question is actually probing.
- Why do so many clients lose this distinction before application code sees it?Because both states deserialize to the same nullish value. A typical JSON binding maps `data` to one nullable field, so a missing key and an explicit null are indistinguishable once parsing is done. Recovering the difference means testing key containment on the raw parsed map, or using a type that wraps optionality around nullability. If the client library hides the body entirely, you have to inspect the response before it deserializes.
- If data is absent, is it safe to retry the operation automatically?Safe, yes — nothing executed, so no side effect can be duplicated. Useful, rarely. The failure was in parsing, validation or variable coercion, so re-sending the identical request produces the identical failure. Retrying an absent-data response is a way to burn quota rather than a way to recover; the fix belongs in the document or the variables.
- Can data be present and null on a query, or only on a mutation?On either. Nothing about the null value is specific to mutations — a query whose root field fails in a way that leaves no usable result serializes `data` as null just the same. Mutations are simply where the distinction has consequences, because a query that ran and returned nothing has cost you a backend call, while a mutation that ran and returned nothing may have changed state.
An absent data key is a letter returned unopened; a null data key is a letter that was opened, acted on, and then the reply was lost.
saying these in an interview costs you the question
- Treats absent data and null data as one case
- Says a null data value proves no resolver ran
- Retries a mutation on any nullish data value
- Thinks data null means the server crashed
- Believes only mutations can return null data
- Assumes an absent data key is a malformed body