skip to content

In GraphQL, when does one failing list element null the entire list?

level: middleimportance: should knowfreq 58%

answer

  1. Two wrappers, two different questions
  2. The element wrapper decides containment
  3. No slot for the null means no list
  4. Lists never quietly get shorter
  5. Failure promoted: field, element, list

basics

~20 s

When the element type is Non-Null. A failing element cannot be represented as null in place, so the error propagates to the list, which becomes null and may keep climbing. With nullable elements the list survives with a null at that index.

solid answer

~40 s

It depends on the **element** wrapper, not the list wrapper. Completing a list value walks the entries and completes each against the inner type. If an entry completes to null and the inner type is nullable, the response gets `null` at that index and the rest of the list is returned intact. If the inner type is Non-Null, there is no slot that can hold the null, so the field error propagates to the list itself and the whole list becomes null — then the list field's own nullability decides whether that null can sit there or the error keeps climbing into the parent. The list is never silently shortened: indices are addressable, and an error's `path` names list positions by 0-indexed integer, so compacting would falsify them.

code

graphql · 16 lines
graphql
type Event {
  id: ID!
  sections: [Section!]!
}

type Section {
  name: String!
  seatsStrict: [Seat!]!
  seatsTolerant: [Seat]!
}

type Seat {
  id: ID!
  row: String!
  priceCents: Int!
}

go deeper

for a junior

Be ready to say that a nullable element leaves a null hole while a Non-Null element takes the list with it, and that a list is never silently shortened.

for a middle

Explain list value completion entry by entry, and keep the two wrappers separate: the element wrapper decides containment, the list wrapper decides whether the dead list can stop there or climbs further.

for a senior

Show you can read a real response backwards — a null at an index versus a null where a list was expected — and know that the error count never measures how many entries actually failed.

for a principal

Own the containment tradeoff across a shared schema: tolerant elements bound the blast radius of one bad row but force a null branch into every consumer that renders the collection.

## The question behind the question "Does one bad row poison the whole list?" has a precise answer, and it is not a runtime decision or a server setting. It is decided by **which of the two wrappers on a list field is Non-Null**: the one around each element, or the one around the list itself. Those are independent, and they answer two different questions. * The **element** wrapper answers: *may an individual slot hold null?* If it may, a failing element leaves a null in place and the list survives. * The **list** wrapper answers: *may this field hold null instead of a list?* If it may, a list that has been destroyed can be replaced by null there and propagation stops. If it may not, the error keeps climbing into the parent object. Read them separately and every case falls out mechanically. ## Completing a list value When execution completes a value against a list type, it walks the entries in order and completes each one against the **inner** type. Each entry is its own propagation site: * An entry completes to null and the inner type is nullable → the response gets `null` at that index and the walk continues. The list keeps its length; the failure is contained in one slot. * An entry completes to null and the inner type is Non-Null → a field error is raised at that index, and it propagates to **the list**. There is no null-shaped slot available, and a list cannot quietly shrink, so the entire list becomes null. * Once the list itself is null, the ordinary rule takes over: if the list field is nullable, null sits there and propagation stops; if the list field is Non-Null, the error climbs into the parent object and keeps going. The crucial move is the second one. Nothing about the failure got worse — one seat's price lookup failed — but the list has no way to represent a missing element, so the only representable outcome is no list at all. ## Why the list never shortens A common wrong answer is that the failing element is skipped and the list comes back one shorter. Execution never does this, and the reason is that indices are meaningful. Clients page, key and render by position; an error entry's `path` addresses list positions by 0-indexed integer. Silently compacting a list would move every element after the failure and make every recorded index a lie. So the choices are exactly two: a null in place, or no list at all. ## The nesting most people miss The failure does not have to be the element itself. It is far more often something *inside* the element. That runs in two stages: 1. Inside the element, ordinary propagation applies. A Non-Null field on the element errors, the error climbs the element's own fields, and if it reaches the element object with nothing nullable to absorb it, the element completes to null. 2. Only now does the element wrapper matter. Nullable element → a null at that index, list intact. Non-Null element → the error escapes into the list, and the list dies. So a `priceCents: Int!` failing on one seat can blank a whole section's seating — not because the list is fragile, but because the failure was promoted from a field, to the element, to the list, one Non-Null wrapper at a time. ## Worked example on a venue graph ```graphql type Section { name: String! seatsStrict: [Seat!]! # element Non-Null, list Non-Null seatsTolerant: [Seat]! # element nullable, list Non-Null } type Seat { id: ID! row: String! priceCents: Int! } ``` A pricing lookup fails for exactly one seat in a section of 1,842. * Selecting `seatsStrict` — the seat completes to null, the element wrapper refuses it, the list becomes null, the list wrapper refuses *that*, and the error lands on `Section`. Every one of the other 1,841 seats is discarded, along with `name`. * Selecting `seatsTolerant` — the seat completes to null, the element wrapper accepts it, and the client receives 1,842 entries of which one is `null`, alongside a single error whose `path` ends in the index of the bad seat. `name` survives. Same failure, same depth, two completely different responses. ## Reading it from the outside From a client's point of view the diagnostic signal is simple. A null **at an index** means the element wrapper was nullable and exactly that element failed. A null **where a list was expected** means an element was Non-Null and one entry took the list down with it — and the count of errors still does not tell you how many elements failed, because propagation stops the walk. The originating error's path is the only thing that names the real culprit. ## The judgement, briefly Element nullability is a decision about how much of a collection one bad row may destroy, and tolerant elements buy containment at the cost of a null branch in every client that renders the list. That trade is a schema-design call and is made once, in the type, long before the failure.

  • The failure is a Non-Null field inside one element rather than the element itself. Does that change the outcome?
    It adds a stage but not a rule. The error first climbs inside the element: if nothing on the path within the element is nullable, the element itself completes to null. Only then does the element wrapper decide — nullable element means a null at that index, Non-Null element means the error escapes into the list. So a single Non-Null leaf on one entry can be promoted, one wrapper at a time, into a null list.
  • Why is the failing element not simply dropped so the list comes back one shorter?
    Because list positions are addressable and meaningful. An error's `path` addresses list entries by 0-indexed integer, and clients key, page and render by position. Compacting the list would shift every later element and make every recorded index wrong. Execution therefore has exactly two representable outcomes: a null in place, or no list at all.
  • Three elements of a nullable-element list fail. How many errors does the client see?
    Three — each failing entry is its own propagation site and contributes its own entry, distinguished by the index in its path. That is the reverse of the Non-Null-element case, where the first failure takes the list down and the walk stops, so one error can accompany the loss of an entire collection. Counting errors never measures how much data was lost.

A nullable element is an egg box with a broken egg removed — you still have a box with a gap at position two. A Non-Null element is a sealed tray: one bad egg and the whole tray is rejected.

saying these in an interview costs you the question

  • Thinks the failing element is dropped and the list shortens
  • Says any element failure always nulls the whole list
  • Confuses the element wrapper with the list wrapper
  • Assumes one error entry means one element failed
  • Believes an empty list is returned instead of null
  • Thinks the server picks containment at runtime

context