skip to content

Apollo

Apollo Client is the full-featured GraphQL client: declarative queries and mutations, a normalized cache, fragments, subscriptions, and a configurable link chain. Its cache is both what makes it powerful and what interview questions concentrate on.

on this pageshow

explore

questions

page 1 of 2

In Apollo Client 4, why does ProductCard declare its own fragment, and how does that fragment reach the product listing page's query?

level: juniorimportance: must knowfreq 45%

answer

  1. fields live beside the renderer
  2. one request for the whole page
  3. spread plus the definition
  4. interpolate the child's gql document
  5. parents compose direct children only

basics

~10 s

ProductCard declares a fragment so the fields it renders live beside the code that renders them. The listing query spreads ...ProductCardFragment and interpolates its definition, so one request fetches every card's fields.

solid answer

~40 s

A component declares a fragment so its data needs sit in its own file: when `ProductCard` starts showing a rating, only that file changes. `ProductCard` exports `PRODUCT_CARD_FRAGMENT`, built with `gql` from `@apollo/client`, and the listing page's query spreads `...ProductCardFragment` inside `products` and interpolates `${PRODUCT_CARD_FRAGMENT}` so the definition travels in the same document. `ProductCardFragment` in turn spreads `...PriceTagFragment` and interpolates that definition, so the page only knows about its direct child. One `useQuery` from `@apollo/client/react` then fetches every card's and price tag's fields in a single request, and the page passes each product down as a prop. Select `id` beside the spread so the cache can identify each product. With `dataMasking` off, which is the default, the page can still read the card's fields, so the coupling is a convention until masking enforces it.

code

tsx · 25 lines
tsx
import { gql } from "@apollo/client";
import { useQuery } from "@apollo/client/react";
import { PRODUCT_CARD_FRAGMENT, ProductCard } from "./ProductCard";

const PRODUCT_LIST = gql`
  query ProductList {
    products {
      id
      ...ProductCardFragment
    }
  }
  ${PRODUCT_CARD_FRAGMENT}
`;

export function ProductListPage() {
  const { data } = useQuery(PRODUCT_LIST);

  return (
    <ul>
      {data?.products.map((product) => (
        <ProductCard key={product.id} product={product} />
      ))}
    </ul>
  );
}

go deeper

for a junior

Recall why a component owns a fragment, and the two steps that put it in a query: the spread and the interpolated definition in the same gql document.

for a middle

Walk the composition chain from PriceTag up to the page query, explain why parents spread only direct children, and name the errors a missing definition or duplicate name produces.

for a senior

Point out that without dataMasking a colocated fragment is only a convention, spot the implicit dependencies it leaves, and decide when a team should turn masking on.

for a principal

Set the team conventions that keep composition healthy: component-prefixed fragment names, direct-children-only spreads, id selected by the parent, and a plan for enforcing boundaries with masking.

## What a fragment does for a component A **fragment** is a named, reusable set of fields on one GraphQL type. In an Apollo Client app the common pattern is to **colocate** a fragment with the component that renders those fields: the `ProductCard` file exports both the component and a fragment on `Product` listing exactly what the card shows. The payoff is ownership. When a designer adds a star rating to the card, the engineer edits one file: the component and its fragment. Nobody has to find every query that feeds product cards and add `rating` to each of them, and nobody deletes a field from a page query without knowing which child still reads it. ## Composing ProductCard and PriceTag into one query On the product listing page the tree is `ProductListPage` → `ProductCard` → `PriceTag`. Each level declares what it renders: 1. `PriceTag` exports `PRICE_TAG_FRAGMENT`: `fragment PriceTagFragment on Product { price compareAtPrice }`. 2. `ProductCard` exports `PRODUCT_CARD_FRAGMENT`, which selects `name` and `imageUrl`, spreads `...PriceTagFragment`, and interpolates `${PRICE_TAG_FRAGMENT}` into its `gql` template so the price tag's definition comes along. 3. The page's `ProductList` query selects `products { id ...ProductCardFragment }` and interpolates `${PRODUCT_CARD_FRAGMENT}`. 4. `useQuery(PRODUCT_LIST)`, imported from `@apollo/client/react`, sends one document that contains the operation and both fragment definitions. 5. The page maps over `data.products` and passes each product to a `ProductCard`, which passes it on to its `PriceTag`. The result is **one network request** for the whole page, shaped by the union of every component's needs, instead of each card fetching its own data. ```graphql query ProductList { products { id ...ProductCardFragment } } fragment ProductCardFragment on Product { name imageUrl ...PriceTagFragment } fragment PriceTagFragment on Product { price compareAtPrice } ``` That is the document Apollo Client sends once the interpolations are resolved. ## What must be true for the composition to work - **Every spread needs its definition in the document.** `gql` interpolation is what puts it there. A query that spreads `...ProductCardFragment` without the definition, and without a fragment registry to supply it, fails: Apollo's cache reports `No fragment named ProductCardFragment`, and a GraphQL server would reject the document anyway. - **Fragment names are unique across the app.** `graphql-tag`, which backs `gql`, warns at runtime when two fragments share a name. Prefixing with the component name (`ProductCardFragment`, `PriceTagFragment`) avoids collisions. - **Parents compose only their direct children's fragments.** The page spreads `ProductCardFragment`, not `PriceTagFragment`; the card owns the decision to render a price tag. If the page spread the grandchild's fragment too, removing `PriceTag` from the card would leave a stale selection in the page. - **The parent selects the key fields.** Selecting `id` next to the spread lets the cache identify each product, which later matters for reading the product with `useFragment`. ## What fragments buy you, and what they do not | Concern | Page query lists every field | Components declare fragments | |---|---|---| | Where a new card field is added | every query that renders cards | `ProductCard.tsx` only | | Network requests for the page | one | one | | Risk of fetching fields nobody renders | grows over time | falls, since each fragment is owned | | Parent reading a child's fields | allowed | still allowed unless `dataMasking` is on | The last row is the gap. By default Apollo Client returns every field in the operation to the component that ran the query, so the page can read `product.price` even though only `PriceTag` asked for it. That creates an **implicit dependency**: if `PriceTag` later drops `compareAtPrice`, any page code that read it breaks without a type error in the file that changed. Apollo Client's `dataMasking` option closes that gap by hiding fragment fields from the parent, and child components then read their own fields with `useFragment`. ## Apollo Client 4 specifics - `gql` is exported from `@apollo/client`; hooks such as `useQuery` come from `@apollo/client/react`, since the core entry point has no React exports in version 4. - Masking is opt-in: `dataMasking` defaults to `false`, so colocated fragments on their own are a convention, not an enforced boundary. - Instead of interpolating definitions, an app can register fragments with `createFragmentRegistry` on `InMemoryCache` and spread them by name; that trades explicit imports for a registration step.

  • PriceTag renders inside every ProductCard. Should the listing page's query spread ...PriceTagFragment itself as well?
    No. `ProductCardFragment` already spreads `...PriceTagFragment`, so the fields arrive. Apollo's guidance is that a parent composes only the fragments of its directly rendered children. If the page also spread the grandchild's fragment, it would depend on the card's internals: when the card stops rendering a price tag, the page query keeps fetching price fields nobody reads.
  • Two files in the app each define a fragment called ProductFields with different fields. What goes wrong?
    Fragment names must be unique across the application. `graphql-tag`, which backs `gql`, warns at runtime that a fragment with that name already exists, and a document that ends up with both definitions is invalid, because GraphQL forbids two fragments with the same name in one document. Prefixing each fragment with its component name avoids this.
  • Why does the listing query select id next to ...ProductCardFragment rather than leaving id to the card's fragment?
    The page is the level that hands each product to a child, so it should own the fields that identify the product. With `id` in the page query, the cache can identify each product whatever the card selects, and if `dataMasking` is later turned on, the object the page passes down still carries `__typename` and `id`, which is what `useFragment` in the card needs to find it.

Colocated fragments work like recipe cards that each list their own ingredients. Before shopping, the cook combines every card's list into one shopping list and makes a single trip, instead of one trip per recipe; changing a recipe means editing only its card.

saying these in an interview costs you the question

  • Each component with a fragment sends its own request for its fields.
  • Spreading ...ProductCardFragment is enough; Apollo finds the definition by its name.
  • The page query should spread every descendant's fragment, down to PriceTag's.
  • Declaring fragments already stops the page from reading the card's fields.
  • Fragment names only need to be unique within one file.
open as a page

In Apollo Client 4, what is a reactive variable created with makeVar, and how would you use one for a shop's cart drawer open flag?

level: juniorimportance: must knowfreq 36%

basics

~20 s

A reactive variable is the function makeVar(initialValue) returns: call it with no argument to read, with one argument to write. It lives outside the cache, and components that read it through useReactiveVar re-render when it changes.

open as a page

In Apollo Client 4, how does InMemoryCache store an author shown on fifty feed posts, and what breaks if the query omits the author's id?

level: juniorimportance: must knowfreq 58%

basics

~20 s

InMemoryCache stores each object with __typename and id once, under a cache ID such as Author:42, and every post references it. Without id in the feed query, each post embeds its own author copy and renames stop propagating.

open as a page

In Apollo Client 4, what does useQuery return for an issue list while loading, on error and on success, and how do you render each?

level: juniorimportance: must knowfreq 62%

basics

~20 s

Apollo Client 4's useQuery returns loading, error, data and dataState, which can overlap: cached rows arrive with loading true, and a failed refetch keeps rows beside error. Render data when present, else the error or a spinner.

open as a page

In Apollo Client 4, what does the useSubscription hook give a notification badge, and when does the badge re-render?

level: juniorimportance: must knowfreq 38%

basics

~20 s

useSubscription, imported from @apollo/client/react, returns loading, data, error and restart. loading stays true until the first event arrives; after that, data holds only the latest event, and the component re-renders as each pushed event lands.

open as a page

In Apollo Client 4, how does useFragment let ProductCard read its fields from the cache, and when does it return complete: false?

level: middleimportance: must knowfreq 40%

basics

~20 s

useFragment is a live, read-only binding to one cached entity: from identifies the product, the hook reads the fragment's fields and re-renders when they change. It never fetches; complete is false while any selected field is missing.

open as a page

In Apollo Client 4, how do you add a locally computed isInCart field to a server Product queried alongside its name and price?

level: middleimportance: must knowfreq 32%

basics

~20 s

Select isInCart @client in the query, give Product.isInCart a read function in InMemoryCache typePolicies, and pass localState: new LocalState() to ApolloClient. Apollo strips the field from the request and fills it from the read function.

open as a page

In Apollo Client 4, after a user deletes a feed post, when do you remove it with cache.evict and cache.gc, cache.modify, or readQuery and writeQuery?

level: middleimportance: must knowfreq 50%

basics

~10 s

cache.evict removes the Post entity everywhere, and cache.gc then drops what became unreachable. cache.modify edits specific fields in place, bypassing merge functions. readQuery and writeQuery rewrite the result of one exact query and variables.

open as a page

In Apollo Client 4, how do you choose between refetchQueries, an update function and the mutation payload to show a newly created issue?

level: middleimportance: must knowfreq 58%

basics

~20 s

In Apollo Client 4 a mutation's normalized payload updates issues already cached but never adds one to a list. Use an update function to insert the new issue without a request, or refetchQueries when the server must decide membership or order.

open as a page

In Apollo Client 4, the Author type has no id and is identified by a unique handle; how do you make InMemoryCache normalize it?

level: middleimportance: should knowfreq 42%

basics

~10 s

Declare the key in a type policy: typePolicies: { Author: { keyFields: ["handle"] } }. Authors are then stored under IDs like Author:{"handle":"ada"}, and every query that selects an Author must also select handle.

open as a page

In Apollo Client 4, how do the cache-first, cache-and-network, network-only and no-cache fetch policies differ for an issue list, and when do you pick each?

level: middleimportance: should knowfreq 52%

basics

~20 s

Apollo Client 4 defaults to cache-first: serve a complete cached result, fetch only on a miss. cache-and-network shows cached rows and always fetches; network-only always fetches but still writes and watches the cache; no-cache bypasses the cache entirely.

open as a page

In Apollo Client 4, how does useMutation's optimisticResponse make a closeIssue click show instantly, and what happens to the cache if the server rejects it?

level: middleimportance: should knowfreq 40%

basics

~20 s

Apollo Client's optimisticResponse writes a guessed result into a separate optimistic cache layer, so the closed row repaints at once. When the server answers or the mutation fails, Apollo drops that layer, keeping the real result or rolling back.

open as a page

In Apollo Client 4, how does subscribeToMore add a newly pushed support-chat message to a room's already-loaded message list?

level: middleimportance: should knowfreq 30%

basics

~20 s

subscribeToMore, returned by useQuery, starts a subscription tied to that query. For each event its updateQuery callback gets the query's cached result and the pushed message, returns a new result with the message appended, and Apollo writes that back to the cache.

open as a page

In Apollo Client 4, after turning on dataMasking, the product listing page's in-stock filter hides every product; why, and what is the right fix?

level: seniorimportance: should knowfreq 34%

basics

~20 s

With dataMasking on, useQuery returns only fields the query selects itself, so inStock, declared only in ProductCardFragment, is undefined and the filter drops everything. Select inStock in the page query; @unmask is the escape hatch.

open as a page

In Apollo Client 4, a search query spreads a fragment on the SearchResult union of Product and Brand, and every hit renders blank; what is missing?

level: seniorimportance: should knowfreq 28%

basics

~20 s

InMemoryCache needs possibleTypes, for example { SearchResult: ["Product", "Brand"] }. Without it, a fragment whose type condition names a union or interface matches no object, so its fields are never written or read and each hit keeps only __typename.

open as a page

An Apollo Client 3 shop passes local resolvers to new ApolloClient and reads cache from the resolver context; what changes when you move them to Apollo Client 4's LocalState?

level: seniorimportance: should knowfreq 22%

basics

~20 s

Resolvers move from the client's resolvers option into new LocalState({ resolvers }) from @apollo/client/local-state, passed as localState. The context becomes { requestContext, client, phase }, so cache is client.cache, and errors, undefined and missing __typename are handled strictly.

open as a page

In Apollo Client 4, the console warns 'Cache data may be lost when replacing the stats field of a Post object'; why does it happen, and what is the safe fix?

level: seniorimportance: should knowfreq 30%

basics

~20 s

Post.stats is an object with no cache ID, so it is stored inline, and each write replaces it, dropping subfields another query selected. Give it an identity with keyFields, or declare merge: true when stats always belongs to its post.

open as a page

In Apollo Client 4, fetchMore with the next cursor returns page two of a feed, yet the screen still shows only page one; what is wrong in the cache configuration?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Without a field policy, Query.feed is stored once per argument set, so page two lands under its own after-cursor key and the original query never sees it. Set keyArgs to exclude the cursor and add a merge function, or use relayStylePagination.

open as a page

In Apollo Client 4, closeIssue returns { id status } and sets refetchQueries: ["OpenIssues"], yet the open-issues page opened next still lists the closed issue; why, and how do you fix it?

level: seniorimportance: should knowfreq 33%

basics

~20 s

The payload updates Issue:42's fields, not the cached issues(status: OPEN) list that references it, and refetchQueries by name refetches only watched queries, so the unmounted OpenIssues is skipped. Remove the issue from that cached list in an update function.

open as a page

Why does an Apollo Client 4 support chat using subscribeToMore show the previous room's messages, some twice, after an agent switches rooms?

level: seniorimportance: should knowfreq 22%

basics

~20 s

The effect that calls subscribeToMore never returns its unsubscribe function, so each room's subscription outlives the switch. useQuery keeps one query with new variables, so old subscriptions write into the current room, and two live subscriptions on one room append each message twice.

open as a page

With Apollo Client 4's GraphQLWsLink, how do you authenticate the support-chat socket and keep messages flowing when the token expires or the socket drops?

level: seniorimportance: should knowfreq 20%

basics

~20 s

GraphQLWsLink ignores context headers, so SetContextLink auth never reaches it; send the token in graphql-ws connectionParams, read at each connection. graphql-ws retries drops itself; once it gives up, useSubscription reports a socket-closed error and completes, so restart and refetch.

open as a page

A shop's React app already uses Apollo Client 4 for server data; when would you keep its cart drawer flag, theme and isInCart in Apollo rather than a separate state library?

level: principalimportance: should knowfreq 20%

basics

~20 s

Keep state in Apollo when it decorates server entities or a query must read it, like isInCart. Small UI flags like the drawer or theme can be reactive variables, but interrelated client state with many writers belongs in a dedicated store.

open as a page

In Apollo Client 4, what does createFragmentRegistry change about how PriceTagFragment reaches queries, and what can go wrong with it?

level: seniorimportance: nice to knowfreq 18%

basics

~10 s

A registry from createFragmentRegistry, passed to InMemoryCache as fragments, lets any query spread ...PriceTagFragment by name; the cache appends the registered definition before sending. Late registration, shadowed definitions and lost typing are the risks.

open as a page

showing 1–30 of 33