API Caching
What It Is
API caching means storing API responses in memory so the app does not need to fetch the same data from the server repeatedly.
It improves performance by reducing latency, avoiding duplicate requests, and decreasing server load.
API Caching = Store fetched API data and reuse it when needed
Why It Matters
Frontend apps often request the same data multiple times.
Without caching, every component or page may trigger a new network request for the same information.
API caching helps by:
- reducing repeated API calls
- making UI updates faster
- decreasing backend load
- improving perceived performance
- sharing fetched data across components
First request -> fetch from server
Next request -> reuse cached response
Common API Caching Libraries
Several frontend libraries provide API caching features.
| Library | Main Use |
|---|---|
| React Query / TanStack Query | Server state caching for React apps |
| SWR | Lightweight data fetching and caching |
| Apollo Client | GraphQL query caching with normalized in-memory cache |
These libraries handle caching, loading states, error states, background refresh, and cache updates.
React Query
React Query, also known as TanStack Query, provides hooks for fetching and caching API data.
It uses a QueryClient to manage cached server state.
QueryClient = central cache manager for React Query
React Query Setup
Create a query client and provide it to the app.
import {
QueryClient,
QueryClientProvider,
useQuery,
} from "@tanstack/react-query";
const queryClient = new QueryClient();
export default function App() {
return (
<QueryClientProvider client={queryClient}>
<Example />
</QueryClientProvider>
);
}
The QueryClientProvider makes the cache available to components inside the app.
React Query Example
Use useQuery() to fetch and cache data.
function Example() {
const { isPending, error, data } = useQuery({
queryKey: ["repoData"],
queryFn: () =>
fetch("https://api.github.com/repos/TanStack/query").then((res) =>
res.json(),
),
});
if (isPending) return "Loading...";
if (error) {
return "An error has occurred: " + error.message;
}
return (
<div>
<h1>{data.name}</h1>
<p>{data.description}</p>
<strong>👀 {data.subscribers_count}</strong>{" "}
<strong>✨ {data.stargazers_count}</strong>{" "}
<strong>🍴 {data.forks_count}</strong>
</div>
);
}
What Happens in React Query
The query is identified using a unique key.
queryKey: ["repoData"];
The data is fetched using a query function.
queryFn: () => fetch(url).then((res) => res.json());
React Query then handles:
- loading state
- error state
- response data
- caching
- reuse of previously fetched data
Same queryKey -> same cached data
SWR
SWR uses a global cache by default.
This allows data to be stored and shared across components.
SWR = Data fetching library with shared cache
SWR also allows custom cache behavior using the provider option inside SWRConfig.
SWR Cache Provider
A cache provider is a Map-like object.
It supports methods like:
| Method | Purpose |
|---|---|
| get | Read cached data |
| set | Store cached data |
| delete | Remove cached data |
| keys | Get all cache keys |
TypeScript-style shape:
interface Cache<Data> {
get(key: string): Data | undefined;
set(key: string, value: Data): void;
delete(key: string): void;
keys(): IterableIterator<string>;
}
SWR Provider Example
The provider option receives a function that returns a cache provider.
import useSWR, { SWRConfig } from "swr";
function App() {
return (
<SWRConfig value={{ provider: () => new Map() }}>
<Page />
</SWRConfig>
);
}
All SWR hooks inside this SWRConfig boundary use the provided cache.
SWRConfig boundary -> shared custom cache provider
Apollo Client
Apollo Client stores GraphQL query results in a local, normalized, in-memory cache.
This allows Apollo to return already cached GraphQL data without sending another network request.
Apollo Cache = Normalized in-memory cache for GraphQL data
Apollo Cache Flow
The first time an app runs a query for data that is not in cache:
Apollo Client -> InMemoryCache
Cache miss -> GraphQL Server
Server returns data
Data stored in cache
Apollo returns data to component
Later, if the same data is requested again:
Apollo Client -> InMemoryCache
Cache hit -> returns cached data
GraphQL Server is not queried
This makes repeated queries much faster.
Apollo Cache Customization
Apollo Client cache is highly configurable.
It can be customized for:
- individual types
- fields in the schema
- local data
- GraphQL query behavior
Apollo can also store and interact with local data that is not fetched from the GraphQL server.
Apollo cache can manage both server data and local data.
Apollo Fetch Policy
By default, Apollo's useQuery() checks the cache first.
If all requested data is available locally, Apollo returns cached data and does not query the GraphQL server.
This default behavior is called:
cache-first
Fetch Policy Example
You can change the fetch policy using the fetchPolicy option.
const { loading, error, data } = useQuery(GET_DOGS, {
fetchPolicy: "network-only",
});
Here, network-only means Apollo does not check cache before making the network request.
Network Policies
Different fetch policies decide how cache and network should be used.
| Policy | Behavior |
|---|---|
| cache-first | Use cache if available, otherwise fetch from network |
| network-only | Always fetch from network |
| cache-and-network | Use cache first and also fetch latest data from network |
| cache-last | Fetch from network first, use cache only if network fails |
| no-cache | Do not read from or write to cache |
cache-first
cache-first checks the cache before making a network request.
Cache available -> use cache
Cache missing -> fetch from network
This is Apollo Client's default fetch policy.
It is useful when cached data is acceptable and fast UI response is important.
network-only
network-only ignores the cache for reading and always sends a network request.
Always fetch from server
Use it when fresh data is more important than cached speed.
In Apollo, network-only may still write the result to cache after fetching.
cache-and-network
cache-and-network returns cached data first if available, while also making a network request.
When fresh data arrives, the cache updates and the component re-renders.
Show cached data quickly
Fetch latest data in background
Update UI when new data arrives
This is useful when you want both fast UI and fresh data.
cache-last
cache-last fetches from the network first.
If the network request fails, then the cache is used as a fallback.
Network first
Cache only if network fails
This is useful when fresh data is preferred but cached data is acceptable as a backup.
no-cache
no-cache bypasses the cache completely.
It does not read from cache and does not write to cache.
No cache read
No cache write
Every request is independent
Use this when every fetch must be separate and cache should not be involved.
API Caching Table
| Area | Explanation |
|---|---|
| Main goal | Store API responses to avoid repeated requests |
| Storage type | Usually in-memory cache |
| Benefit | Lower latency and server load |
| React Query | Uses query keys and query functions |
| SWR | Uses global or custom cache providers |
| Apollo Client | Uses normalized GraphQL in-memory cache |
| Default Apollo policy | cache-first |
| Freshness control | Managed through fetch policies and cache behavior |
Basic Checklist
Cache API responses that are requested repeatedly
Use unique query keys for cached data
Use React Query for server state caching in React apps
Use SWR for lightweight cached data fetching
Use Apollo Client for GraphQL caching
Understand cache-first behavior
Use network-only when fresh server data is required
Use cache-and-network for fast UI plus background refresh
Use no-cache when cache should not be used at all
Avoid stale data by choosing the right fetch policy
Interview Style Answer
API caching stores responses from API calls in memory so the application does not need to fetch the same data from the server repeatedly. This improves performance by reducing latency and server load. Libraries like React Query, SWR, and Apollo Client provide caching capabilities. React Query uses a QueryClient and useQuery() with a unique queryKey and queryFn to fetch and cache data. SWR uses a global cache by default and can be customized with a Map-like cache provider through SWRConfig. Apollo Client stores GraphQL query results in a local normalized in-memory cache and uses fetch policies like cache-first, network-only, cache-and-network, cache-last, and no-cache to control whether data comes from cache, network, or both.
One-Line Summary
API Caching = Store API responses in memory so repeated data requests can be served faster with fewer server calls.
Final Mental Model
React Query -> queryKey based API cache
SWR -> shared cache provider
Apollo -> normalized GraphQL cache
cache-first -> fast cached response
network-only -> always fresh
cache-and-network -> fast now, fresh later
no-cache -> no cache involvement