There's an old joke that there are only two hard problems in computer science: cache invalidation, naming things, and off-by-one errors. The joke survives because caching is everywhere — it's the single most effective way to make things fast — and getting it right is genuinely tricky. Let's make it simple.
What caching is
Caching means storing the result of expensive work so you can reuse it instead of redoing it. Compute a page once, serve the stored copy a thousand times. Fetch an image once, reuse it on every visit. The fastest work is the work you don't repeat.
On the web, caching happens at three layers, each closer to the user than the last.
The three layers
| Layer | Where it lives | Caches |
|---|---|---|
| Browser | The user's device | Assets, so repeat visits are instant |
| CDN / edge | Servers near users | Content, served without touching your origin |
| Server / app | Your backend | Computed results, query results, rendered pages |
Browser cache
The browser stores assets (images, CSS, JS) locally, so a returning visitor doesn't re-download them. You control this with HTTP cache headers (Cache-Control, ETag) — telling the browser how long it may reuse a file.
CDN / edge cache
A CDN caches your content at locations near users. The first request fetches from your origin; everyone after is served from the nearby edge — fast for the user, and a huge load off your origin.
Server / application cache
On your backend, you cache the expensive stuff: database query results, rendered pages, API responses (often in an in-memory store). This is what slashes your TTFB — instead of rebuilding a page on every request, you serve a cached copy.
TTL: how long to cache
Every cache entry has a lifespan — a TTL (Time To Live):
- Long TTL → faster (fewer recomputes/refetches), but changes take longer to show.
- Short TTL → fresher, but less benefit.
The classic pattern: cache static assets (with content-hashed filenames) for a very long time, and cache dynamic content for short, sensible windows.
The hard part: invalidation
Here's the catch. The flip side of caching is stale content: you update something, but caches keep serving the old version until they expire.
Cache invalidation is the hard problem — knowing when to throw away a cached copy. Cache too long and users see stale data; cache too short and you lose the benefit. The common solution is content-hashed filenames (e.g.
app.a1b2c3.js): when the content changes, the filename changes, so the old cache is naturally bypassed.
For pages and APIs, you'll either set short TTLs or actively purge the cache when the underlying data changes.
A monitoring gotcha
Caching can hide problems: a cached page can look perfectly healthy while your origin is broken underneath. That's why it's worth occasionally checking the origin directly (with no-cache requests) so a stale cached copy doesn't mask an outage your users will hit once the cache expires.
The bottom line
| In one line | |
|---|---|
| What | Store a result once, reuse it many times. |
| Three layers | Browser (device), CDN (edge), server (backend). |
| TTL | Long = fast but stale-prone; short = fresh but less gain. |
| Hard part | Invalidation — use content-hashed filenames and purging. |
Caching is the closest thing to free speed on the web — it's how fast sites stay fast under load. Cache at all three layers, set sensible TTLs, and have a plan for invalidation, and you get the speed without serving stale content.
Related: How to speed up your website, What is a CDN?, TTFB.