skip to content

In GraphQL execution, what happens when one selection set selects the same field twice?

level: middleimportance: should knowfreq 46%

answer

  1. Collection happens before any resolver runs
  2. Grouping is by the key in the response
  3. An alias changes that key
  4. Sub-selection sets of a group are merged
  5. Validation guarantees the group is compatible

basics

~20 s

The executor groups a selection set by response key before resolving anything, so both selections land in one group and the field resolves once. Their sub-selections are merged, so the response carries a single key.

solid answer

~40 s

Execution does not walk the document selection by selection. It first runs a collection step that builds an ordered map from **response key** — the alias if there is one, otherwise the field name — to the list of selections sharing that key. `soilMoisture` written twice yields one entry holding two selections, so the field's resolver is called once. When that field returns an object, the sub-selection sets of every selection in the group are collected together, so the children union rather than one winning. A validation rule that runs before execution guarantees the grouped selections name the same schema field with identical arguments, which is what makes resolving once sound. Aliases break the grouping deliberately: two response keys are two entries and two resolutions, even for the same field.

code

graphql · 12 lines
graphql
query PaddockReadings {
  sensor(id: "snsr_4471") {
    soilMoisture
    ...Freshness
    dry: soilMoisture
  }
}

fragment Freshness on Sensor {
  soilMoisture
  lastReadingAt
}

go deeper

for a junior

Know the observable outcome: selecting a field twice in one selection set gives one key in the response, not two, and the server does the work once. You will not usually write that by hand, but composed fragments do.

for a middle

Walk the collection step out loud: response key equals alias or field name, selections group under that key, their sub-selection sets merge, and one resolver call serves the whole group.

for a senior

Use it when reasoning about cost. Repetition across composed fragments is free because the merge is guaranteed; aliases are not, because each alias is a new response key and a new resolution of the same field.

for a principal

Frame it as an API-surface property: fragment composition scales precisely because merging is part of the execution algorithm, so many independent views can consume one schema without each of them adding backend work.

## Execution does not read the document field by field Before a single resolver runs for an object, the executor performs a collection step over that object's selection set. Its output is a **grouped field set**: an ordered map whose keys are *response keys* and whose values are lists of field selections. Only then does the executor iterate that map, calling field resolution once per entry, and the response object it builds carries exactly that map's keys, in that map's order. The response key is defined simply: the selection's alias if it has one, otherwise the field's name. Everything surprising about duplicate selections falls out of those two sentences. ## What collection does with each selection Walking the selection set in order, collection handles three kinds of selection. - **A field.** If `@skip(if: true)` or `@include(if: false)` applies, the selection is dropped and nothing further happens to it. Otherwise it is appended to the list under its response key — creating the entry if this is the first selection with that key, joining the existing list if it is not. - **A fragment spread.** The named fragment is looked up. If its type condition does not apply to the object type being executed, the spread contributes nothing. Otherwise its selection set is collected recursively and the resulting groups merge into the current grouped field set: the fields land at the level of the spread, under their own response keys, not inside a namespace of their own. Collection also remembers which named fragments it has already visited, so it never walks one twice. - **An inline fragment.** The same treatment, with the type condition written in place; an inline fragment with no type condition always applies. "Flattening" is the word for the fragment cases. However deeply fragments are composed, the fields they contribute end up in one flat map at the level where the composition happened. ## Merging, and why resolving once is sound Two selections under one key produce one entry, so field resolution runs once. When the field's type is an object, the executor then collects the *sub*-fields of every selection in the group together: the child selection sets are concatenated and collected as one, which is why the children of the two selections union in the response instead of one winning. On a farm sensor graph, `sensor { soilMoisture ...Freshness }` where `Freshness` also selects `soilMoisture` yields one `soilMoisture` key, one call to that field's resolver, and — if it were an object type — the union of both selections' children beneath it. That is safe only because same-key selections are compatible, and a validation rule enforces compatibility before execution: selections sharing a response key must name the same schema field with identical argument values, or the document is invalid. Because that check has already passed, the executor can resolve the group using the first selection's field name and coerced arguments and know that the others would have produced the same call. ## Aliases are the deliberate exception An alias is not decoration; it is a new response key, therefore a new entry in the grouped field set, therefore a separate resolution with its own path. `soilMoisture` and `dry: soilMoisture` on the same object resolve the field twice. That is the point of aliases: it is how a client asks for the same field with *different* arguments — `today: readings(window: DAY)` beside `week: readings(window: WEEK)` — which the merge rule would otherwise forbid outright. It is also why repetition under aliases carries a cost that repetition under one key does not. An expensive field aliased forty times is forty resolutions of an expensive field, each with a distinct path. Bounding that is a server-side control the specification leaves entirely open. ## Why this matters more than it looks The merge guarantee is what makes fragment composition viable as a client architecture. A dashboard whose tiles each contribute a fragment will select `id`, `serial` and `soilMoisture` from a dozen places in one document. Without merging, that would be a dozen resolutions of each field and a response with repeated keys. Because collection happens first and groups by key, the composed document costs what a hand-written one costs, and adding a thirteenth tile that needs `serial` adds nothing at execution time. This is specified execution behaviour, not a client-library optimisation — a client that merged nothing locally would still receive one key. There is a corollary for server code. Anything that reasons about "the fields requested beneath this field" has to reason about the *merged* set, not the first selection it happens to be handed. ## Interview framing State the mechanism, not the observation. "You get one key" is the outcome. "Collection groups the selection set by response key before anything resolves, sub-selection sets of a group are merged, and an alias is what breaks the grouping" is the answer.

  • How many times does a field's resolver run if three fragment spreads at the same level each select it?
    Once. Fragment spreads are flattened during collection: the fields inside them are appended to the grouped field set at the level where the spread appears, under their own response keys. Three spreads selecting `soilMoisture` therefore produce one group with three selections and one resolver call. That is what makes per-view colocated fragments cheap to compose — repeating a field across fragments costs nothing at execution time.
  • What stops two selections in one group from disagreeing about their arguments?
    A validation rule rejects the document before execution when same-response-key selections are not mergeable — different field names, or the same field with different argument values. Because that static check has already passed, the executor can resolve the group once using the first selection's field name and coerced arguments. It is a rule about the document, not a decision the executor makes at run time.
  • Does aliasing the same field ten times cause ten resolutions?
    Yes. Each alias is a distinct response key, so collection produces ten entries and the executor resolves the field ten times, each under its own path. Repetition under one key is free; repetition under ten keys is not, which makes an expensive field's aliases a real amplification vector. Bounding that is a server-side control the specification leaves open.

Collection is a mailroom sorting letters into pigeonholes by address before anyone opens one: five letters to the same box go up in a single trip, but a different address means a separate trip however similar the contents.

saying these in an interview costs you the question

  • Thinks the response carries the duplicated key twice
  • Says the resolver runs once per selection in the document
  • Believes an alias merges with the un-aliased field
  • Thinks the later duplicate selection overwrites the earlier one
  • Assumes fragment spreads resolve in their own separate pass
  • Calls the merge a client-library trick rather than execution

context