What is a selection set in a GraphQL document, and when must a field carry one?
answer
- Braces mark what you asked for
- The schema decides where nesting can continue
- Composite keeps going, leaf stops
- One rule, two symmetrical violations
- Caught with the schema, not by the parser
basics
~20 sA selection set is the brace-delimited list of fields a GraphQL document requests at one level. A field whose type is an object, interface or union must carry one; a field whose type is a scalar or enum must not.
solid answer
~50 sEvery pair of braces in an executable document is a **selection set** — the fields requested at that point in the tree. Nesting continues for as long as the selected field's type is *composite*: an object, an interface or a union. It stops at a **leaf**: a scalar or an enum. The specification makes that symmetry a validation rule, Leaf Field Selections: a composite-typed field written without a selection set is invalid, and a leaf-typed field written *with* one is equally invalid. In a freight-tracking graph, `shipment { legs { terminal { code } } }` is legal because `shipment`, `legs` and `terminal` are all object types and `code` is a `String`. Writing `code { value }` or writing `terminal` bare both fail. Neither is a syntax error — both parse fine and are rejected during validation, before any resolver runs.
code
graphql · 13 linesquery ShipmentRoute($id: ID!) {
shipment(id: $id) {
reference
status
legs {
sequence
terminal {
code
city
}
}
}
}go deeper
Be ready to define a selection set in one sentence and to say both halves of the rule out loud: composite types need braces, scalars and enums must not have them. This is a first-screen question and a hesitant answer is noticed.
Explain the mechanics: which output types count as composite, why list and non-null modifiers do not change where the selection set goes, and why both violations are validation errors rather than syntax errors.
Show you understand the consequence: because the client chooses the depth and there is no wildcard, the document is the only statement of how much work the request implies. Be ready to connect that to bounding request cost on a public endpoint.
Own the design argument for why GraphQL refuses a wildcard selection at all: a request that names every field it wants is what makes cost analysis, field-level usage tracking and safe deprecation possible across many client teams.
## A request is a tree, not a path A GraphQL document does not name a resource; it draws a tree over the schema's graph. Each pair of braces is a **selection set** — the list of fields being requested at that one point in the tree — and every field inside it may open a selection set of its own. The operation itself carries the root selection set, whose members are the fields declared on the root operation type. So the document's shape *is* the response's shape. Reading a document top to bottom tells you the keys of the JSON that comes back, level for level, with no schema lookup needed — which is the property most of GraphQL's tooling is built on. ## Where nesting stops The schema decides where the tree can keep growing. GraphQL divides output types into two groups: * **Composite types** — object, interface and union types. These *have* fields, so a selection over them is meaningful. * **Leaf types** — scalars (`Int`, `Float`, `String`, `Boolean`, `ID`, and any custom scalar) and enums. These have no fields at all; the value is the whole answer. The specification turns that into one validation rule with two halves, named **Leaf Field Selections**: 1. If the field's type is composite, its selection set must be present and non-empty. 2. If the field's type is a leaf, it must have no selection set. Both halves are strict. There is no “select the object and get all of its fields” shorthand — GraphQL deliberately has no `*`, because a server can only bound the work a request causes if the request says exactly what it wants. And there is no way to reach *inside* a scalar: if a custom scalar carries structured JSON, the client receives that value whole and picks it apart itself. ## Nullability and list modifiers do not change the rule A field's declared type may be wrapped: `Leg`, `Leg!`, `[Leg!]!`. Those wrappers say whether the value may be null and whether it is a list; they do not change what the value *is made of*. The selection set is written once, against the underlying named type, and the server applies it to every element of a list. A field typed `[TrackingEvent!]!` takes one selection set, not one per event — there is no syntax for asking different fields of the third element than of the first. ## Two symmetrical errors ```graphql # invalid: `code` is a String — a leaf cannot be selected into shipment(id: "BKG-40913") { legs { terminal { code { value } } } } # invalid: `terminal` is an object type — it needs a selection set shipment(id: "BKG-40913") { legs { terminal } } ``` Both of these **parse**. The grammar permits a selection set after any field, so the lexer and parser are content; it takes the schema to know that `code` is a `String` and `terminal` is an object type. That is why both are *validation* errors rather than syntax errors, and it is a distinction worth being able to state: the same document text can be valid against one schema and invalid against another, so a client cannot decide legality from the document alone. Because validation runs before execution, the whole request is rejected. Nothing resolves, and the failure is a property of the document, not of any one field's data. ## Depth is the client's choice, and that is the tradeoff Nothing in the rule caps how deep a tree may go. A schema with a cycle — a shipment has legs, a leg has a carrier, a carrier has shipments — lets a client write a legal document of essentially unbounded depth over a modest type system. Selection sets are what make GraphQL precise about what the client wants; they are also what make an unguarded endpoint easy to abuse, which is why depth and cost limiting exist as a separate concern. ## The meta-field exception that is not one `__typename` is available on every composite type and is typed `String!`. It is a leaf like any other, so it never carries a selection set — it is simply a field the schema did not have to declare. ## What to say in an interview Define the selection set as the brace-delimited request at one level; say nesting continues while the type is composite and stops at scalars and enums; state both halves of the rule; and add that neither violation is a syntax error — they are caught by validation, which needs the schema, before a single resolver runs.
- Is a scalar field written with braces rejected by the parser or by validation, and why does that distinction matter?By validation. The grammar allows a selection set after any field, so the document parses; only the schema knows the field is a scalar. It matters because legality is schema-dependent: the identical document text is valid against a schema where that field is an object type. A client cannot decide from syntax alone, which is why validation is a separate pass that needs the schema.
- A field is typed as a list of objects. How many selection sets does the document write for it?One. List and non-null are modifiers on the type, not composite types in their own right, so the selection set is written once against the underlying named type and the server applies it to every element. There is no syntax for selecting different fields from different elements of the same list.
- Why is there no way to ask for every field of an object at once?Deliberate design. GraphQL has no wildcard selection because the request is also the server's only statement of how much work to do; a schema-wide `*` would let one short document pull an unbounded tree. It also breaks the guarantee that the document's shape predicts the response's shape, which typed clients and codegen depend on.
A selection set is a nested order form: for a line item that is itself a form, you must fill in the sub-form; for a line item that is a single number, there is nothing inside to fill in.
saying these in an interview costs you the question
- Thinks braces are optional styling on any field
- Says a scalar field with braces just returns null
- Expects an object field with no braces to return everything
- Believes the parser catches a scalar selection set
- Writes one selection set per element of a list
- Assumes some wildcard selects all fields of a type