skip to content

questions

4

In a nested GraphQL connection, does first: 10 mean ten rows total or ten per parent?

level: juniorimportance: must knowfreq 58%

answer

  1. Ask where the argument is applied
  2. A list resolves its children per element
  3. Counts multiply down a path
  4. Twenty-three parents, twenty-three connections

basics

~20 s

Ten per parent. A nested connection field is resolved once for every object in the outer page, and its arguments slice that parent's own children, so an outer page of 23 with first: 10 inside can return 230 leaf rows.

solid answer

~50 s

Per parent. GraphQL completes a list by resolving the child selection set once for each element, so a connection nested under another connection is a separate execution per parent object, each carrying the arguments written once in the document. Asking a freight graph for `shipments(first: 23)` and, inside each shipment node, `scanEvents(first: 10)` requests the first ten scan events **of each of the twenty-three shipments** — up to 230 leaf rows, arriving as twenty-three independent connection objects, each with its own edges, pageInfo and cursors. Nothing in GraphQL, or in the Relay server convention that defines connections, expresses "ten across all parents"; a flat cross-parent page has to be modelled as its own root field over its own ordering. Because the counts multiply down a path rather than adding, the default and maximum page size at each level compose into the worst-case response size.

code

graphql · 13 lines
graphql
query ShipmentBoard {
  shipments(first: 23) {
    edges {
      node {
        waybill
        scanEvents(first: 10) {
          edges { node { scannedAt facilityCode } }
          pageInfo { hasNextPage endCursor }
        }
      }
    }
  }
}

go deeper

for a junior

Be ready to say plainly that a nested connection's arguments apply once per parent object, and to multiply the numbers out loud: an outer page of twenty-three with first: 10 inside is up to 230 leaf rows, not ten.

for a middle

Explain the mechanism rather than the outcome: list completion resolves the child selection set once per element, so one written argument becomes one execution per parent, each producing its own connection object with its own page state.

for a senior

Show that you read the product as a capacity number. The response size and the backend work a document implies come from multiplying page sizes down the path, and inner defaults matter more than inner maxima because most documents omit the argument.

for a principal

Own the schema consequence: if nesting multiplies, the defaults and maxima set at each level are one composed budget rather than independent knobs, and a genuine cross-parent page belongs in a root field instead of being emulated by nesting.

## What the execution algorithm actually does GraphQL execution is defined over the **document**, not the schema. The engine resolves a root field, then resolves the sub-selection written under it for the value that came back. The clause that decides this question is list completion: when a field's type is a list, the engine completes it by resolving the child selection set **once for every element**. One selection set is written; N executions of it happen. A connection is an ordinary object type with ordinary fields, so nothing about it changes that. In a freight-tracking graph: ```graphql query ShipmentBoard { shipments(first: 23) { edges { node { waybill scanEvents(first: 10) { edges { node { scannedAt facilityCode } } } } } } } ``` `scanEvents(first: 10)` is written once and executed twenty-three times — once per shipment node in the outer page — and every one of those executions receives `first: 10`. The argument is bound to the **field occurrence**, and the field occurrence exists once per parent object. So the answer is ten per shipment, up to 230 scan events in one response. ## There is no construct for a cross-parent page Nothing in the GraphQL specification, and nothing in the Relay server convention that defines connections (a convention, not part of the language specification), provides a way to say "ten scan events in total, drawn from whichever shipments they happen to belong to". Arguments are per field occurrence; a field occurrence under a list is per parent; there is no third thing. If a flat page is what the product actually needs — a control-tower board of the ten most recent scans across the whole fleet — that is a different ordered result set and belongs in its own root field: ```graphql query RecentScans { scanEvents(first: 10, filter: { originHub: "ROT-04" }) { edges { node { scannedAt waybill } } } } ``` Merging twenty-three per-parent pages on the client does not reproduce it. Each per-parent page is ordered *within* its parent; the union of twenty-three "first tens" is only guaranteed to contain the global first ten if you fetched enough of each, and the cursors of one parent's connection cannot be interleaved with another's. ## The response shape makes it obvious ```json {"data":{"shipments":{"edges":[ {"node":{"waybill":"AWB-8814","scanEvents":{"edges":[{"node":{"scannedAt":"2026-08-14T09:12:00Z"}}],"pageInfo":{"hasNextPage":true}}}}, {"node":{"waybill":"AWB-9207","scanEvents":{"edges":[],"pageInfo":{"hasNextPage":false}}}} ]}}} ``` There are twenty-three `scanEvents` objects, not one. Each carries its own `edges`, its own `pageInfo` and its own cursors, describing that shipment's children only. ## The number that matters is a product Twenty-three parents times ten children is 230 leaf nodes. Add a third paged level — each scan event's `attachments(first: 4)` — and it is 23 x 10 x 4 = 920. Page sizes **multiply down a path**; they do not add. A document is therefore not "a page", it is a product of pages, and the same product is roughly what the backend has to produce, whatever technique is used to avoid issuing one call per row. ## Defaults multiply more often than maxima do Most real documents never pass `first` on the inner field at all. Whatever default the server applies is the number that actually multiplies in production. A nested connection whose default is "all children" turns a twenty-three-row outer page into an unbounded response the moment one shipment has 41,900 scan events. Choosing the inner default is choosing the worst case for every client that omits the argument. ## Aliases multiply too Response keys, not field names, define the response. `recent: scanEvents(first: 10)` and `flagged: scanEvents(first: 30)` under the same shipment are two independent field occurrences with two independent argument sets, resolved separately and both counted. Nothing deduplicates them, because they legitimately mean different slices. ## The mental model to carry away Every nested connection is a paged list in its own right, belonging to one parent, with its own cursor state and its own end. "Page two" of a nested list is a per-parent notion. The outer connection pages parents; the inner connection pages one parent's children; the two have nothing to do with each other except that one sits inside the other and multiplies its size. ## What a weak answer sounds like The common wrong answer is that `first: 10` bounds the response — that the server will return ten rows and stop. It will not: it will return up to ten rows per parent, and the only bound on the whole response is the product of every page size on the path. The second common wrong answer is that the server "flattens and merges" the nested lists into one page. It does not; twenty-three separate connection objects come back, and any flattening is the client's own work on data that was never globally ordered.

  • Where would a request for the ten most recent scan events across all shipments live in the schema?
    In a root field of its own, such as `scanEvents(first: 10, filter: { originHub: "ROT-04" })`. That is one ordered result set with one pageInfo and cursors that are comparable to each other. Nesting cannot produce it: a nested connection is per parent by construction, and merging twenty-three per-parent pages on the client gives neither the correct global order nor a usable cursor.
  • If the nested first argument is optional, what does the server return when a client omits it?
    Whatever default that field applies — GraphQL defines none, and the Relay server convention does not either. That default is the number that multiplies in practice, because most documents omit the argument. A nested connection whose effective default is "every child" turns a modest outer page into an unbounded response as soon as one parent is unusually large.
  • Does selecting the same nested connection twice under two aliases double the work?
    Yes. The response is keyed by response key, so `recent: scanEvents(first: 10)` and `flagged: scanEvents(first: 30)` under one shipment are two independent field occurrences with two independent argument sets. Both are resolved, both are fetched and both count towards the response size; nothing merges them, because they legitimately denote different slices.

It is the difference between asking twenty-three warehouses for their ten newest arrivals each and asking head office for the ten newest arrivals in the network. Only the second is one list.

saying these in an interview costs you the question

  • Thinks first: 10 caps the whole response
  • Expects one pageInfo shared by all nested lists
  • Assumes nested row counts add rather than multiply
  • Believes the server flattens per-parent pages into one
  • Treats cursors from one parent as valid for another

context

open as a page

For a nested GraphQL connection, why can't one lookup with a single row cap serve first: 10 per parent?

level: seniorimportance: must knowfreq 52%

basics

~10 s

A cap on the whole lookup is a global cap: its rows can all belong to one parent, leaving the rest with empty pages and hasNextPage false. Each parent needs its own top-N slice.

open as a page

In a nested GraphQL connection, how does a client page one parent's children further?

level: middleimportance: should knowfreq 41%

basics

~20 s

With a second operation that fetches just that one parent and passes that parent's own endCursor to its nested connection. Each nested connection has its own pageInfo and cursors, meaningful only inside the connection that produced them.

open as a page

How do you set page-size caps across nested GraphQL connections when each level multiplies the leaf count?

level: principalimportance: should knowfreq 31%

basics

~20 s

Budget the product, not each field. Caps that look reasonable in isolation compose multiplicatively, so shrink defaults and maxima with depth, decide per field whether a nested connection is offered at all, and roll any tightening out behind usage telemetry.

open as a page