Skip to content
intermediate Phase 6 · Variables, Aliases, Fragments, and Directives

Named Fragments for Field Reuse

Extracting shared selection sets into fragments used across many queries.

40m
1 problems
Topic Progress 0%

Selection Sets That Drift Apart

Selection Sets That Drift Apart

Two screens need the same nine fields on User, so each query declares them:

query Profile { user(id: $id) { id name avatarUrl bio handle joinedAt location } }
query Sidebar { me { id name avatarUrl bio handle } }

The sidebar shipped first and stops at avatarUrl. Later the profile adds handle — and then a designer notices the sidebar header renders nothing, because a second copy of the field list nobody remembered is still missing it. Duplication across documents has no compiler watching it, so drift is the default outcome rather than an accident.

A named fragment collapses the copies:

fragment UserCard on User {
  id
  name
  avatarUrl
  bio
  handle
}

query Profile($id: ID!) {
  user(id: $id) { ...UserCard joinedAt location }
}

query Sidebar { me { ...UserCard } }

handle now has exactly one declaration, and every consumer picks it up on the next request. The fragment is still just a selection set: execution cost, resolver calls, and response shape are identical to writing those fields inline, because the server expands spreads before it runs anything. The win is purely in maintenance — one place to change, many places to benefit.

The same argument applies across a team. Once a field list is written twice, nobody can tell which copy is authoritative: code review approves one, a test exercises the other, and the fragment is the only structure that gives those fields a single owner.

Fragments also shrink reviews. Adding a field to UserCard is one line in one file; without fragments the same change is a diff in every query that renders a user, each needing its own reasoning about whether that screen wants the field at all — and that reasoning is exactly what gets skipped under time pressure.

Declare Once, Spread Anywhere

Declare Once, Spread Anywhere

Spreads work inside any selection set, at any depth, including inside other fragments:

query Dashboard($id: ID!) {
  user(id: $id) {
    ...UserCard
    posts(first: 5) {
      ...PostPreview
      author { ...UserCard }    # same fragment, different path
    }
  }
}

Four validation rules are the ones you will actually hit:

  1. The type condition must fit. Spreading fragment PostPreview on Post where the selection's type is User fails with Fragment cannot be spread here. A fragment declared on an interface or union may be spread wherever one of its members appears, which is what makes polymorphic reuse work.
  2. Fragment names are unique per document. Two definitions named UserCard in one operation collide at validation, not at execution.
  3. Every defined fragment must be used. An unused definition is a validation error, so dead fragments cannot survive a refactor quietly.
  4. No fragment may reach itself. A fragment that spreads into itself through a nested field fails the no-cycles rule — see circular queries and cycle guards for why that rule exists.

Because spreads merge into the surrounding selection, fragments leave no trace in the JSON: the response contains the union of the fields, under the parent's own key. That property is what lets UserCard appear at the top level of one query and three levels deep in another while the client keeps reading a plain object.

One practical habit: fragments describe UI pieces ("what a user card renders"), not whole types. A fragment that selects everything on User reintroduces over-fetching under a new name.

Two consequences of merging are worth knowing before you refactor. First, fragments in the same selection set are checked by the ordinary field-merging rule: if two of them select posts with different limits, the document fails validation exactly as inline selections would, so shared fields belong in one definition rather than two overlapping ones. Second, a fragment can sit inside an inline fragment, which is how a polymorphic query reuses a concrete-type selection — see inline fragments.

Where Fragments Live in a Real Client

Where Fragments Live in a Real Client

Two ownership models, both running in production codebases:

Model Shape Wins when
Colocated fragment defined beside the component that renders it many teams; screens evolve independently
Centralized one fragments/user.graphql imported everywhere small surface; the API team reviews what is exposed

Whichever you pick, tooling reads the same documents. Codegen turns each fragment into a TypeScript type containing exactly the selected fields, so a UI change and its type change travel together — see TypeScript codegen. Normalized caches use fragment definitions when deciding whether a stored record can satisfy a query, which is why the same fragment drives rendering and cache reads — see response caching strategy.

When fragments stop helping:

  • A three-field fragment used once is indirection, not reuse — inline it.
  • One enormous fragment spread everywhere over-fetches again; scope fragments to components.
  • Duplicating a fragment per variation is the drift problem returning. Conditional presence belongs to directives — see include and skip directives.
  • Renaming or deleting a fragment changes generated types across every consumer; treat the fragment name as an interface.

Interview angle: explain why GraphQL's schema validation does not catch duplicated selection sets — validation checks each document in isolation, so two documents with divergent copies of the same selection both pass. Reuse is a project-level discipline, and fragments are the mechanism.

A naming rule keeps the strategy honest: name fragments after the component that renders them (UserCard), never after the type alone. Type-named fragments attract fields until they become the everything-fragment that both ownership models warn about, at which point every consumer over-fetches together.

Practice Problems

0 / 1 solved
Extract a shared selection into a fragment
Named fragment reuse

Two operations select overlapping user fields and have already drifted apart. Define one fragment that satisfies both screens, rewrite the queries to spread it, and explain what stays identical after the change: resolver calls, response shape, or both.

Example:

Input: query Profile { user(id: "7") { id name avatarUrl bio handle joinedAt } } query Sidebar { me { id name avatarUrl bio } } # missing handle — drifted

Output: fragment UserCard on User { id name avatarUrl bio handle } query Profile($id: ID!) { user(id: $id) { ...UserCard joinedAt } } query Sidebar { me { ...UserCard } }

handle now has one declaration, so both screens receive it. Fields shared by both queries live in the fragment; the profile-only field stays in its own selection.

Optimal Solution — O(1) time, O(f) space

Group fields by the UI piece that renders them, not by the type that owns them; keep screen-specific fields outside the fragment.

fragment UserCard on User {
  id
  name
  avatarUrl
  bio
  handle
}

query Profile($id: ID!) {
  user(id: $id) {
    ...UserCard
    joinedAt
  }
}

query Sidebar {
  me {
    ...UserCard
  }
}

Edge Cases:

  • Spreading the fragment where the type is Post — rejected with "Fragment cannot be spread here"
  • Defining UserCard twice in one document — duplicate name validation error
  • An unused fragment definition — NoUnusedFragments rejects the document

Quiz

1. fragment PostPreview on Post is spread inside a selection of type User. What happens?

Question 1 options

2. What does a fragment change about the response JSON?

Question 2 options

3. A document defines fragment Unused on User but never spreads it. What does the server do?

Question 3 options

4. Do fragments reduce the number of resolver calls a query makes?

Question 4 options

Flashcards

Question

Named fragment syntax

Answer

fragment Name on Type { fields } — applied with ...Name inside any selection set of a compatible type.

Question

Fragment validation rules

Answer

Type condition must fit; names unique per document; every fragment used; no cycles.

Question

Fragments and the response JSON

Answer

Spreads vanish at execution — fields merge into the parent selection with no wrapper or key.

Question

Fragment scope rule

Answer

Scope fragments to UI pieces, not whole types: an everything-fragment just over-fetches under a new name.

Question

Colocated vs centralized fragments

Answer

Colocated: evolves with the component. Centralized: one reviewable surface. Both are read by codegen and caches.

Revision Notes

Key Takeaways

  • 1. Duplicated selection sets drift because validation checks each document in isolation
  • 2. A fragment is a named selection set on one type, spread with ... and expanded before execution
  • 3. Four validation rules cover fit, uniqueness, usage, and cycles
  • 4. Fragments shape maintenance and tooling, not execution cost or response structure

Interview Tips

  • • State precisely what a fragment changes (document reuse, generated types, cache reads) and what it does not (resolver calls, JSON shape)
  • • Walk the validation errors in order: wrong type condition, duplicate name, unused fragment, cycle
  • • Discuss fragment ownership as a team-scale decision, not a syntax preference — colocate or centralize with a reason

Cheat Sheet

Named Fragments Cheat Sheet

fragment UserCard on User { id name avatarUrl bio handle }

query Profile($id: ID!) { user(id: $id) { ...UserCard joinedAt } }
query Sidebar            { me { ...UserCard } }
  • Spread anywhere a selection set is allowed, including inside other fragments
  • Validation: type condition fits · unique names · all used · no cycles
  • Response = merged fields, no fragment key in the JSON
  • Scope to UI pieces; one fragment per type invites over-fetching
  • Aliases go on the field: viewer: user { ...UserCard }
  • Codegen types and cache reads both derive from the fragment definition

Continue Learning