How does caching work in GraphQL and why is it harder than REST?
Learn how GraphQL caching works with normalized caches, DataLoader, and persisted queries, and why single-endpoint POST makes it harder to cache than REST.
Expected Interview Answer
GraphQL caching mainly happens at the client normalized-cache level and the field or resolver level, rather than through HTTP URL caching, because GraphQL typically sends every request as a POST to a single endpoint, so URLs are not unique per query and standard HTTP caches cannot key on them.
In REST each resource has a distinct URL and GET, so CDNs, browsers, and proxies cache responses natively by URL. GraphQL breaks that because one endpoint serves arbitrarily shaped queries. Clients like Apollo and Relay solve it with a normalized cache that stores objects by a stable identifier (typically __typename plus id) so overlapping queries share cached entities. Servers add per-field caching, dataloaders to batch and dedupe, persisted queries to enable GET-based CDN caching, and cache-control hints per field.
- Normalized client caches dedupe entities across queries
- DataLoader batches and caches within a request to avoid N+1
- Persisted queries allow CDN-level GET caching
- Per-field cache-control gives fine-grained TTLs
- Avoids over-fetching so cached data stays lean
- Automatic UI updates when a cached entity changes
AI Mentor Explanation
REST caching is like storing full match highlight reels each labeled by a fixed match number you can grab instantly. GraphQL is like fans requesting custom clip compilations of any players and overs, so no two requests match a pre-made reel. Instead you index every individual delivery by a unique ball ID, and rebuild any compilation from those shared cached clips.
Step-by-Step Explanation
Step 1
Recognize the URL problem
GraphQL POSTs to one endpoint, so responses lack unique cacheable URLs like REST resources have.
Step 2
Normalize on the client
Clients store each object by __typename + id so overlapping queries reference the same cached entity.
Step 3
Batch on the server
Use DataLoader to batch and dedupe field fetches within a request, preventing N+1 queries.
Step 4
Persist queries
Register queries so clients can send a short hash via GET, enabling CDN and HTTP caching.
Step 5
Add field cache hints
Attach cache-control (maxAge, scope) per field so a gateway can cache responses appropriately.
What Interviewer Expects
- Explains why single-endpoint POST defeats HTTP URL caching
- Describes normalized client caching by typename and id
- Mentions DataLoader for batching and per-request caching
- Knows persisted queries enable GET/CDN caching
- Aware of per-field cache-control hints
Common Mistakes
- Assuming GraphQL caches natively via HTTP like REST GETs
- Confusing normalized caching with simple response caching
- Forgetting DataLoader solves the N+1 problem
- Not knowing persisted queries make GET-based CDN caching possible
- Ignoring cache invalidation when a mutation changes an entity
Best Answer (HR Friendly)
“In REST every resource has its own web address, so browsers and CDNs cache it automatically. GraphQL usually sends everything to one address as a POST, so that automatic caching does not work. Instead GraphQL caches smartly on the client by storing each object by its ID, and uses tricks like persisted queries and batching on the server.”
Code Example
import { InMemoryCache } from '@apollo/client';
const cache = new InMemoryCache({
typePolicies: {
User: {
keyFields: ['id'] // objects normalized by __typename + id
}
}
});import DataLoader from 'dataloader';
const userLoader = new DataLoader(async (ids) => {
const users = await db.users.findByIds(ids); // one batched query
return ids.map(id => users.find(u => u.id === id));
});
// resolver
author: (review) => userLoader.load(review.authorId)type Post {
id: ID!
title: String! @cacheControl(maxAge: 300)
}Follow-up Questions
- How does a normalized cache decide two queries share an object?
- What are persisted queries and how do they help CDN caching?
- How does DataLoader prevent the N+1 problem?
- How do you invalidate cached data after a mutation?
MCQ Practice
1. Why can't standard HTTP caches easily cache GraphQL like REST?
GraphQL sends varied queries as POST to a single endpoint, so URL-based HTTP caching cannot key on them.
2. How do clients like Apollo normalize their cache?
Normalized caches key entities by __typename + id so overlapping queries reuse the same cached object.
3. What is DataLoader primarily used for?
DataLoader batches field loads and caches within a single request, eliminating N+1 database calls.
Flash Cards
Why is GraphQL HTTP caching hard? — One POST endpoint means no unique URLs for standard HTTP/CDN caches to key on.
How does a normalized client cache key objects? — By __typename + id, so overlapping queries share entities.
What does DataLoader solve? — Batches and dedupes field fetches within a request, avoiding N+1 queries.
How can GraphQL use CDN caching? — Persisted queries send a hash via GET, giving a cacheable URL.