skip to content

In GraphQL, what does an argument on a nested field apply to when its parent resolves to many objects?

level: seniorimportance: should knowfreq 38%

answer

  1. The document is a template, not a call
  2. Executed once per parent object
  3. Nothing scopes downward through the tree
  4. Multiply by the fan-out above it
  5. A default on a nested list is expensive

basics

~20 s

It applies once per parent object, independently. The argument is written once in the document, but the nested field is executed for every parent, so the same values are used again for each one - never once across the whole response.

solid answer

~50 s

A nested field is executed once for every object its parent field produced, and the argument values written in the document are applied afresh each time. `accounts { statements(year: 2024, page: 1) }` over 312 accounts means 312 independent applications of `year: 2024`, each yielding that account's own first page - not one shared page and not 312 statements in total. Two consequences follow. **Arguments do not cascade**: an argument on a parent field scopes nothing for the fields nested under it, and the document language offers no way to reference one argument from another. **Argument effects multiply with depth**: a nested list field with `limit: Int = 47` under 312 parents can yield 14,664 items from one written argument, so defaults on nested list fields are a much larger commitment than the same default at the root. A document cannot vary the value per parent.

code

graphql · 10 lines
graphql
query MonthlyStatements {
  customer(id: "cus-4471") {
    accounts {
      iban
      statements(year: 2024, page: 1) {
        closingBalanceMinor
      }
    }
  }
}

go deeper

for a junior

Recall the rule rather than the consequences: a nested field runs once for each object its parent produced, and the argument you wrote once is used again for every one of them.

for a middle

Explain why nothing cascades. Each argument list is local to its field, a parent's arguments scope nothing below, and a document has no syntax for varying a value per parent object.

for a senior

Do the arithmetic out loud. A nested list argument multiplies by the fan-out above it, defaults count even though the caller never typed them, and that product is where an unexpectedly heavy document comes from.

for a principal

Own the review rule. Decide where limiting arguments are mandatory and how nested defaults get sized against realistic parent counts, so no single deep document can commit the service to work nobody planned for.

## One written argument, many applications A document is a template, not a call. When a selection set is executed against a list of parent objects, every field in that selection set is executed once **per parent object**, and the argument values written next to the field are used again for each of those executions. They are not consumed once and shared. Take a bank statements graph: ```graphql query MonthlyStatements { customer(id: "cus-4471") { accounts { iban statements(year: 2024, page: 1) { closingBalanceMinor } } } } ``` If the customer holds 7 accounts, `statements(year: 2024, page: 1)` runs 7 times. Each run gets that account's own first page of 2024 statements. The response carries 7 separate statement lists. Neither "one page of statements shared by the accounts" nor "one page spanning the accounts" is what happened. This sounds obvious written down, and it is precisely the thing candidates get wrong on a whiteboard, because the argument *looks* singular - it is one piece of text, sitting in one place in the document. ## You cannot vary the value per parent The direct consequence: within one selection, every parent gets the same argument values. There is no syntax for "12 statements for the current account, 3 for the savings one". A document can select the same field twice under different response keys, giving each selection its own arguments, but that still applies each set uniformly across all parents. When genuinely per-parent parameterisation is needed, the answer is a schema change rather than a document trick: expose a root field that takes a list of identifiers alongside their per-item parameters, so the varying values arrive as one input value rather than as arguments scattered through the tree. ## Arguments do not cascade The second consequence surprises people more. An argument on a parent field scopes **nothing** for the fields beneath it. In ```graphql accounts { statements(year: 2024) { transactions(minAmountMinor: 5000) { amountMinor } } } ``` the `year: 2024` written on `statements` does not reach `transactions`. There is no inheritance rule, no name matching, and no way for one field's arguments to reference another's. If `transactions` should be restricted to 2024, either it must be reachable only through a statement that is already scoped to 2024 by the shape of the data, or it needs its own argument. Servers can and do carry information down their own internal call chain - the object a parent produced is available when its children are resolved, and that is usually how a child field ends up knowing which year it is inside. But that is server-side plumbing, invisible in the type system. From the document's point of view each argument list is closed and local, and a schema that relies on a reader *assuming* a parent argument scopes its children is a schema that will be misread. ## The multiplicative blast radius The operational point, and the reason this is a senior question rather than a trivia one: **the cost of an argument on a nested field multiplies by the fan-out above it**. A `limit: Int = 47` on a root list field promises at most 47 items. The same declaration on a field nested under a list of 312 accounts promises up to 14,664 - from a document that wrote the word `limit` zero times, because the default supplied it. Nest that one level further and the ceiling multiplies again; a deeply nested document, twenty levels of it, can turn several small, individually reasonable defaults into a response nobody sized for. This changes how you pick argument defaults during schema review. At the root, a generous default is a convenience. On a nested list field, the same generous default is a promise made once per parent, and it is worth asking what the realistic parent count is before writing the number down. The same applies to whether a nested list field has a limiting argument at all: an unbounded nested list is unbounded *per parent*. It also changes how you read a slow request. When one document is far heavier than the shape of its text suggests, the arithmetic to do first is the product of the fan-outs and the nested arguments, and the defaults matter as much as what the caller actually wrote - a default is invisible in the document but fully present in the work. ## The mental model to state in an interview Say it as a rule: *arguments are written per field and applied per parent occurrence of that field.* Everything else follows - no cascading, no per-parent variation, and a cost that is the product of the argument's effect and the fan-out above it, not the sum.

  • How would you let a caller request different values per parent object?
    Not through nested arguments - the document has no way to express it. Change the schema instead: expose a root field that takes a list input, where each entry carries an identifier and that entry's parameters, so the varying values arrive as one structured input value. Selecting the same nested field twice under different response keys gives you two sets of values, but each set still applies uniformly to every parent.
  • Does an argument on a parent field constrain the fields nested under it?
    No. Each argument list is local to its own field; there is no inheritance, no name matching and no way for one field's arguments to reference another's. A child field is only scoped by the object its parent produced, which is server-side plumbing rather than anything the type system states. If a nested field needs a constraint, it needs its own argument.
  • Why does a default value on a nested list field deserve more scrutiny than the same default at the root?
    Because it is applied once per parent rather than once per request. A limit of 47 on a root field caps the response at 47 items; the same limit on a field nested under 312 objects caps it at 14,664, and the caller never wrote the argument at all. Choosing that number is a capacity decision, so it should be made against a realistic parent count.

One instruction printed on a batch of parcels - "put twelve pages in each" - is not twelve pages in total. It is twelve times however many parcels there are.

saying these in an interview costs you the question

  • Thinks a nested argument applies once per response
  • Expects parent arguments to filter nested fields
  • Believes a document can vary a value per parent
  • Sizes a nested limit as if fan-out were one
  • Ignores defaults when estimating a document's cost
  • Assumes argument values are consumed after first use

context