Lesson 26 of 30
A single top-level GraphQL query listing every field every component needs works fine on day one and turns into a maintenance problem by month three — fragments fix that by letting each component own its own data requirements.
13 min read
Every option explains itself — why the right one is right, and why each wrong one is wrong. Miss one and it comes back later on a spaced-repetition schedule.
Question 1 of 3
A component deep in the tree needs to render a new field it doesn't currently show. In a codebase using colocated GraphQL fragments, where does that change happen?
Hints without spoilers, and a plain explanation of why a wrong answer was wrong — answered only from this lesson. Free account, no card.
Sign up freeWhat interviewers actually ask about this, and what a strong answer has to cover.
A strong answer covers
Likely follow-ups
A GraphQL query and a REST endpoint solve the shape-of-data problem differently, but a real GraphQL codebase runs into a second problem that has nothing to do with over- or under-fetching: where does a query actually live?
The naive approach is one big query at the top of a page, listing every field every component in that page's tree needs:
query ProfilePage($id: ID!) {
user(id: $id) {
name
avatarUrl
bio
followerCount
joinedAt
}
}
That works, until a component three levels deep needs a new field. Its own rendering code doesn't declare what data it needs — some unrelated file at the top of the tree does. Adding a field to a deeply nested component means going and finding that top-level query, editing it, and hoping you didn't also disturb a field some other component was relying on. This is a real, common source of "why is this field missing" bugs: the component's data requirement and the component's rendering code live in two different places that nobody keeps in sync on purpose.
Fragments fix this by letting each component declare its own data requirement, colocated with (defined right next to) the component that uses it:
fragment UserAvatarFields on User {
name
avatarUrl
}
// UserAvatar.tsx — the fragment lives right next to the component that needs it
const UserAvatarFields = `
fragment UserAvatarFields on User {
name
avatarUrl
}
`;
function UserAvatar({ user }: { user: { name: string; avatarUrl: string } }) {
return (
<div>
<img src={user.avatarUrl} alt={user.name} />
<span>{user.name}</span>
</div>
);
}
// FollowerCount.tsx — a completely separate component, its own fragment
const FollowerCountFields = `
fragment FollowerCountFields on User {
followerCount
}
`;
function FollowerCount({ user }: { user: { followerCount: number } }) {
return <span>{user.followerCount} followers</span>;
}
# ProfilePage's query composes the fragments instead of listing raw fields
query ProfilePage($id: ID!) {
user(id: $id) {
...UserAvatarFields
...FollowerCountFields
}
}
The page-level query no longer lists name, avatarUrl, and followerCount directly — it just spreads in the fragments each child component already declared. If UserAvatar later needs to render a join date too, that change happens entirely inside UserAvatar.tsx: add the field to its own fragment, and it flows up into the composed query automatically. Nobody has to hunt down ProfilePage's query to make a change that's really about how one leaf component renders.
avatarUrl is the same component rendering it — a purely presentational child further down the tree just receives whatever shape its parent's fragment already fetched."How would you structure GraphQL queries in a large component tree?" is a real, practical question, and naming fragment colocation — data requirements living with the component that uses them, composing upward into one query — is what separates a candidate who's actually maintained a GraphQL codebase from one who's only used it to fetch a single flat object.