How does caching improve performance?
The Short Answer
Caching stores copies of data or computed results closer to where they're needed, so future requests can be served faster without repeating expensive operations. On the web, caching happens at multiple layers — browser memory, HTTP cache, CDN edge nodes, application-level caches, and database query caches. Each layer trades freshness for speed, and the art is choosing the right cache strategy for each type of data.
Layers of Caching
A single web request can hit multiple cache layers before reaching the origin server. Each layer is progressively further from the user but closer to the source of truth. A cache hit at any layer short-circuits the rest of the chain.
- Browser memory cache
- In-memory, fastest, cleared on tab close
- Stores decoded images, parsed scripts, fetch responses
- Browser disk cache (HTTP cache)
- Persists across sessions, controlled by Cache-Control headers
- Stores full HTTP responses keyed by URL
- Service Worker cache
- Programmable cache you control via Cache API
- Enables offline support and custom caching strategies
- CDN / Edge cache
- Geographically distributed, serves static assets near the user
- Reduces latency and origin server load
- Application cache (Redis, Memcached)
- Server-side, caches computed results or database queries
- Avoids repeated expensive computations
- Database query cache
- Database-level caching of query results
- Transparent to the application layer
HTTP Caching Headers
The browser's HTTP cache is controlled by response headers from the server. These headers tell the browser how long to keep a response, whether to revalidate before using it, and whether intermediate caches (CDNs, proxies) can store it.
# Cache for 1 year (immutable static assets with hashed filenames)
Cache-Control: public, max-age=31536000, immutable
# Cache for 5 minutes, then revalidate with server
Cache-Control: public, max-age=300, must-revalidate
# Don't cache at all (sensitive data, real-time content)
Cache-Control: no-store
# Cache but always revalidate before using (API responses)
Cache-Control: no-cache
# (confusing name — it DOES cache, but checks freshness every time)
# ETag for conditional requests (revalidation)
ETag: "abc123"
# Browser sends: If-None-Match: "abc123"
# Server responds: 304 Not Modified (use cached version)
The most effective pattern for static assets: use content-hashed filenames (app.a1b2c3.js) with max-age=31536000, immutable. The hash changes when the file changes, so the browser caches forever and gets a new URL when there's an update. No revalidation needed.
Caching Strategies
Different types of data need different caching strategies. The right strategy depends on how often the data changes, how critical freshness is, and how expensive it is to regenerate.
| Strategy | How it works | Best for |
|---|---|---|
| Cache-first | Serve from cache, only fetch if cache miss | Static assets, fonts, images |
| Network-first | Try network, fall back to cache on failure | API data that should be fresh but work offline |
| Stale-while-revalidate | Serve stale cache immediately, refetch in background | Data that's OK slightly stale (user profiles, feeds) |
| Network-only | Always fetch from network, never cache | Real-time data (stock prices, live scores) |
| Cache-only | Only serve from cache, never fetch | Pre-cached offline content |
Frontend Caching Patterns
Beyond HTTP caching, frontend applications implement their own caching layers. React Query and SWR maintain an in-memory cache of API responses. Memoization caches computed values. These application-level caches give you fine-grained control over freshness and invalidation.
// React Query: automatic caching + stale-while-revalidate
const { data } = useQuery({
queryKey: ['user', userId],
queryFn: fetchUser,
staleTime: 5 * 60 * 1000, // Fresh for 5 min (no refetch)
gcTime: 30 * 60 * 1000, // Keep in cache for 30 min
});
// Manual memoization for expensive computations
const expensiveResult = useMemo(() => {
return heavyComputation(data);
}, [data]);
// Simple in-memory cache for repeated function calls
const cache = new Map<string, Response>();
async function cachedFetch(url: string): Promise<Response> {
if (cache.has(url)) return cache.get(url)!;
const response = await fetch(url);
cache.set(url, response.clone());
return response;
}
Cache Invalidation
The hardest part of caching is knowing when to throw away stale data. Serve stale data too long and users see outdated content. Invalidate too aggressively and you lose the performance benefit. There are several approaches to invalidation, each with tradeoffs.
- Time-based (TTL)
- Cache expires after a fixed duration
- Simple but can serve stale data until expiry
- Event-based
- Invalidate when a mutation happens (user updates profile → clear profile cache)
- Precise but requires tracking dependencies
- Version-based
- Content-hashed URLs (app.abc123.js) — new content = new URL
- Perfect for static assets, not applicable to API data
- Conditional revalidation
- ETag / Last-Modified — ask server if cache is still valid
- Saves bandwidth (304 response has no body) but still requires a round-trip
The two hard problems in CS
"There are only two hard things in Computer Science: cache invalidation and naming things." — Phil Karlton. Cache invalidation is genuinely difficult because you're balancing performance (serve fast) against correctness (serve fresh). There's no universal solution — the right approach depends on your data's update frequency and tolerance for staleness.
Why Interviewers Ask This
This question tests your understanding of web performance optimization at multiple levels. Interviewers want to see that you know the different cache layers (browser, CDN, server), understand HTTP cache headers and what they control, can choose appropriate strategies for different data types, and appreciate the cache invalidation challenge. It shows you think about performance holistically — not just code optimization but infrastructure and network-level improvements.
Quick Revision Cheat Sheet
Cache layers: Browser memory → HTTP disk cache → Service Worker → CDN → Server cache
Static assets: Content-hashed filenames + max-age=31536000, immutable
API responses: no-cache (always revalidate) or short max-age + stale-while-revalidate
Sensitive data: no-store — never cache
SWR pattern: Show stale data instantly, refetch in background, update when fresh
Invalidation: TTL, event-based, version-based, or conditional (ETag/304)