skip to content

questions

3

Why can a GraphQL document nest arbitrarily deep when the schema has finitely many types?

level: juniorimportance: must knowfreq 62%

answer

  1. The schema is a graph, not a list
  2. Some fields point back at their own type
  3. Finitely many types, unlimited documents
  4. The one cycle rule is about fragments
  5. The cap lives in validation, not the spec

basics

~20 s

Object types can reference each other, so the type graph contains cycles. A finite schema therefore admits documents of unlimited depth: each trip round a cycle adds one selection level, and the GraphQL specification sets no depth limit.

solid answer

~50 s

Depth is a property of the document, not of the schema. In a pedigree schema where `Animal.sire` returns an `Animal`, the selection `sire { sire { sire { tag } } }` is legal, and so is the same chain four hundred levels deep — two types that merely point at each other are enough. GraphQL's validation rules cover correctness, not size or cost; the only cycle rule in the specification is that **fragment spreads must not form cycles**, which exists so fragment expansion terminates and is trivially sidestepped by unrolling the nesting by hand. Cost explodes only when the cycle passes through a list field: `offspring { offspring { … } }` multiplies rows at every level. So servers add a maximum-depth check as an extra validation rule — a near-universal convention, not a specified feature.

code

graphql · 11 lines
graphql
type Animal {
  id: ID!
  tag: String!
  sire: Animal
  dam: Animal
  offspring(first: Int = 25): [Animal!]!
}

type Query {
  animal(tag: String!): Animal
}

go deeper

for a junior

Be ready to state the core fact in one sentence: types can reference each other, so a caller can nest a selection as far as they like, and nothing in the schema stops them. Being able to sketch a self-referencing type is enough here.

for a middle

Explain the mechanics — depth belongs to the document, the fragment-cycle rule bounds only fragments, and a maximum-depth check is an added validation rule that runs before execution. Say explicitly which of that is specified and which is convention.

for a senior

Show that you separate a linear to-one chain from a multiplicative chain through list fields, and that you know a depth cap is a proxy measure. Expect to be asked what it fails to catch and what it wrongly rejects.

for a principal

Own the framing that the specification defines validity, not resource policy, and that every limit here is house policy your organisation must set, document and version. Be ready to argue what belongs in the schema's shape versus in a runtime limit.

## A finite schema, an infinite set of legal documents A GraphQL schema declares a finite list of types, and that fact misleads people into assuming the set of legal documents is bounded too. It is not. An object type may declare a field whose type is another object type — including itself, directly or through a chain of other types. The schema is a **graph**, and graphs have cycles. A livestock pedigree service is the clearest case, because the domain itself is recursive: ```graphql type Animal { id: ID! tag: String! sire: Animal dam: Animal offspring(first: Int = 25): [Animal!]! } ``` One object type, four fields. `Animal.sire` returns an `Animal`, so `sire { sire { sire { tag } } }` is a legal selection — and so is the same construction four hundred levels deep. Nothing in the schema says how many times a caller may go round the cycle, because **depth is a property of the document, not of the schema**. Two object types that merely reference each other (`Herd.animals` and `Animal.herd`) are enough; a single self-referencing field is not required. ## The specification does not bound it, and only one of its rules is about cycles GraphQL's validation rules are about *correctness*: a selected field must exist on the type, an argument must have the right type, a declared variable must be used, a fragment must be spreadable on the type it lands on. None of them is a size, depth or cost rule. The specification defines what a valid document *means*; it deliberately says nothing about what a server should be willing to *run*. The one cycle rule people half-remember is real, but narrower than they think: **fragment spreads must not form cycles**. This is invalid — ```graphql fragment Ancestry on Animal { tag sire { ...Ancestry } } ``` — because `Ancestry` spreads itself transitively, and a validating server rejects the document. That rule exists so that fragment expansion and validation terminate, not as an abuse control. The proof is that you can write the identical nesting by hand, unrolled, as deep as you can type it, and the document is perfectly valid. So the specification closes exactly one route to an unbounded document and leaves the obvious one open. ## Why deep nesting costs, and when it does not Depth is expensive along two very different axes, and a good answer separates them. **Parsing and validation** are roughly linear in document size. A three-hundred-level chain is a few kilobytes of text; parsing it is not what hurts you. Naively recursive validators or serializers can blow a stack, but that is an implementation bug, not the resource story. **Execution** depends entirely on whether the cycle passes through a *list* field. Nesting through a to-one field — `sire { sire { sire } }` — resolves one record per level, so three hundred levels is three hundred single-row lookups: slow and serial, but linear. Nesting through a list field is multiplicative. If a recorded sire averages 12 offspring, then `offspring { offspring { offspring … } }` reaches roughly 12^n nodes: about 249,000 at five levels and over 400 million at eight. That is the shape a depth cap is really a proxy for, and saying so is what separates a memorised answer from an understood one. ## What servers do about it, and what it is called Every mainstream GraphQL server ships a maximum-depth check, and it is a **widespread convention, not a specified feature** — say that plainly, because interviewers listen for it. It is implemented as an additional validation rule: after the document parses, walk each operation's selection sets, track the longest root-to-leaf path, and reject the request if it exceeds the configured cap. Because it fails during validation, the *shape* of the failure is specified even though the rule is not. A document that fails validation is answered with an `errors` entry and **no `data` key at all** — the request never entered execution, so there is no partial data to report. That is a different response shape from a resolver throwing mid-execution, which yields `data` with a null hole and a field error beside it. ## Counting depth correctly Depth must be measured on the *flattened* document. A fragment spread contributes its own nesting at the point where it is spread, so a six-level fragment spread inside a selection already five levels deep is eleven. Counting only the literal brace nesting in the operation body under-reports, and it is the single most common bug in a hand-rolled limiter — an attacker only has to move the deep part into a fragment. ## What a depth cap does not solve It bounds one dimension. A document that is two levels deep and eight hundred fields wide slips under any depth cap, and a legitimately deep traversal — twelve generations of ancestry — is rejected by a cap tuned for a flat schema. Depth is a floor, not the whole control.

  • Is nesting three hundred levels through a to-one field as dangerous as three hundred through a list field?
    No, and the difference is the whole point. A to-one chain resolves one record per level, so cost grows linearly — three hundred serial lookups. A chain through a list field multiplies: at roughly 12 offspring per animal, five levels is about 249,000 nodes and eight is over 400 million. A depth cap is a crude proxy for the second case, which is why weighted schemes score list fields differently from scalar hops.
  • Where in request handling does a maximum-depth check run, and what does the client get back?
    It runs as an extra validation rule after parsing and before execution, so no resolver is ever invoked. Because the failure is a validation failure, the response follows the specified shape for one: an `errors` entry and no `data` key at all — not a `data` object with nulls, which is what a resolver throwing during execution would produce.
  • Does forbidding fragment cycles mean a self-referencing type is illegal?
    No. The rule constrains documents, not schemas. `Animal.sire: Animal` is a perfectly legal type definition and is the normal way to model recursive domains. What is invalid is a named fragment on `Animal` that spreads itself, directly or through other fragments, because expanding it would not terminate.

Two mirrors facing each other are a finite amount of glass, but the corridor of reflections between them has no end. The schema is the glass; the document chooses how far down the corridor to look.

saying these in an interview costs you the question

  • Claims the specification defines a maximum query depth
  • Thinks a finite schema implies bounded documents
  • Believes the fragment-cycle rule stops deep nesting
  • Says self-referencing types should be banned from a schema
  • Assumes parsing cost, not execution fan-out, is the danger
  • Counts depth only in the operation body, ignoring fragments

context

open as a page

Why does a GraphQL depth cap need a field-count cap and a body-size cap alongside it?

level: middleimportance: should knowfreq 44%

basics

~20 s

A depth cap bounds only the longest path. A shallow document can still select hundreds of fields at every level, and any document must be parsed in full before validation runs, so breadth and raw bytes each need their own limit.

open as a page

You add a maximum-depth rule to a public GraphQL API and real traffic starts failing. How do you set the cap?

level: seniorimportance: should knowfreq 51%

basics

~20 s

Measure before enforcing. Log the depth of real documents through a full traffic and release cycle, then set the cap well above the legitimate maximum. Expect introspection and connection-wrapped traversals to be the deepest legitimate documents.

open as a page