HTTP Caching and Headers

6 min read

Reading Progress0%
Web Backend Fundamentals Index
Tier 1 -- Core Web & API Basics
Tier 2 -- Communication Models
Tier 3 -- Production & API Hardening
Web Backend Fundamentals Index
Tier 1 -- Core Web & API Basics
Tier 2 -- Communication Models
Tier 3 -- Production & API Hardening

HTTP Caching and Headers

1. What It Is

caching is a mechanism that allows responses to be stored and reused, so the same data doesn't have to be fetched from the origin server on every request. It is controlled through headers that tell browsers, CDNs, and proxies what to cache, for how long, and when a cached response is stale.

Caching is one of the highest-leverage performance optimizations in backend systems. A well-cached API can serve millions of requests with trivial server load. Poor caching leads to redundant database queries, slow responses, and unnecessary infrastructure cost.


QUICK CHECK

A backend API endpoint fetches user profile data from a database and is called thousands of times per minute, but the profile data rarely changes. After enabling HTTP caching with appropriate headers, the team notices database query volume drops by over 90%. What is the most direct reason for this reduction?

Choose one answer

2. How It Works in Practice

The two types of caching

1. Expiration-based caching — the server tells the client how long a response is fresh.

HTTP/1.1 200 OK
Cache-Control: max-age=3600

The client caches this response and reuses it for 3600 seconds (1 hour) without contacting the server.

2. Validation-based caching — the server sends a fingerprint; the client asks "has this changed?"

The server sends a fingerprint:

HTTP/1.1 200 OK
ETag: "abc123"
Last-Modified: Mon, 14 Apr 2025 10:00:00 GMT

On the next request, the client sends the fingerprint back:

GET /products/5 HTTP/1.1
If-None-Match: "abc123"

If unchanged, the server responds with 304 Not Modified and no body — saving bandwidth. If changed, it returns 200 with the new body and a new ETag.

Key Cache-Control directives

DirectiveMeaning
max-age=NCache is fresh for N seconds
no-cacheMust revalidate with the server before using cached response
no-storeNever cache this response (sensitive data)
privateOnly the end user's browser may cache; CDNs must not
publicAny cache (CDN, proxy) may store this response
s-maxage=NOverride max-age for shared caches (CDNs) only
must-revalidateOnce stale, must revalidate before serving — don't serve stale data
immutableContent will never change; skip revalidation even after max-age

Real example for a static asset:

Cache-Control: public, max-age=31536000, immutable

One year, cached by CDN, never revalidated (used for hashed filenames like app.a3f8c2.js).

Real example for an API endpoint:

Cache-Control: private, max-age=60

Per-user data, cached in the browser for 60 seconds but not by CDNs.

The Vary header

Vary tells caches to store separate versions of a response based on request headers.

Vary: Accept-Encoding, Authorization

This means: cache a different version for compressed vs. uncompressed responses, and per-auth-token. Without Vary: Authorization, a CDN might serve one user's private data to another user.


QUICK CHECK

A CDN is serving an API endpoint that returns personalized user dashboard data. The response currently has the header Cache-Control: public, max-age=300. What is the most significant risk of this configuration?

Choose one answer

3. Common Real-World Usage

CDN caching — A CDN like Cloudflare, Fastly, or CloudFront reads your Cache-Control headers and caches responses at edge nodes globally. A Cache-Control: public, max-age=300 on a product listing page means the CDN serves it for 5 minutes without touching your origin server.

Browser caching — Static assets (CSS, JS, images) are served with long max-age values (up to 1 year) combined with content hashes in filenames. When you deploy a new version, the filename changes, busting the cache.

API caching — Endpoints that return relatively stable data (product catalog, configuration, user profile) can be cached privately for short durations to reduce database load.

Conditional requests — Used for polling or large resources. A periodically checks GET /config and only processes the response if the ETag changed — saving bandwidth and processing.


QUICK CHECK

Your team deploys a new CSS file but users are still seeing the old styles because their browsers cached the previous version. Which strategy correctly solves this problem while still allowing long-term browser caching for future deployments?

Choose one answer

4. Trade-offs and Common Mistakes

Caching private data publicly

Returning Cache-Control: public on a user-specific response means CDNs will serve one user's data to every subsequent requester. Always use private for personalized responses.

Forgetting Vary: Accept-Encoding

If your server supports gzip compression, responses vary by encoding. Without this header, a CDN might cache the compressed version and serve it to a client that doesn't support gzip.

Setting too-long max-age without cache busting

If you cache main.js for 1 year without a hash in the filename, users won't get updates for a year. Always combine long max-age with content-addressed filenames for static assets.

Confusing no-cache and no-store

  • no-cache does NOT mean "don't cache" — it means "cache it but always revalidate."
  • no-store means "don't store this anywhere."
  • Use no-store for sensitive responses (payment pages, authentication tokens).

Cache invalidation for stale data

caches are passive — they expire but aren't explicitly invalidated by your application by default. If you update a resource, clients with cached copies see stale data until expiry. Options:

  • Use short max-age for frequently changing data
  • Use CDN cache-purge APIs (Cloudflare, Fastly) to actively invalidate on update
  • Implement ETag/Last-Modified revalidation so clients always get fresh data when it changes

Not caching at all

Many teams skip caching entirely and rely on a fast database. This works at small scale but fails under load. Even a 10-second cache on a hot endpoint can absorb enormous traffic spikes.


QUICK CHECK

A payment processing page returns sensitive authentication tokens in its response. A developer wants to ensure this data is never stored by browsers or intermediate caches. Which Cache-Control directive should they use, and why?

Choose one answer

5. Interview Angle

How to explain it

" caching uses response headers to tell clients and intermediaries like CDNs how long they can reuse a response without hitting the server. There are two models: expiration (max-age) where the client trusts the cached copy for a duration, and validation (ETag/If-None-Match) where the client checks if the resource has changed. The Cache-Control header is the main tool. Getting caching right can dramatically reduce origin server load and improve response times."

Common interview questions

Q: What is the difference between no-cache and no-store?

no-cache says "you can store the response, but you must revalidate with the server before serving it again." no-store says "don't cache this at all — never store it on disk or in memory." Use no-store for sensitive data like financial records.

Q: What is an ETag and how does it work?

An ETag is a server-generated fingerprint (usually a hash) of the resource content. The server sends it in the ETag header. On subsequent requests, the client sends If-None-Match: <etag>. If the content hasn't changed, the server returns 304 with no body. This saves bandwidth and lets clients cache aggressively while still getting fresh data when things change.

Q: Why do static asset URLs often include a hash like app.a3f8c2.js?

So we can set a very long max-age (like 1 year) safely. When the content changes, the build system generates a new hash, which creates a new URL. The old cached version is effectively abandoned because no one will request that URL anymore. This is called cache busting.

Q: When would you use s-maxage?

When CDN caching should differ from browser caching. For example: Cache-Control: public, max-age=0, s-maxage=600 tells the browser not to cache (max-age=0 means stale immediately) but lets a CDN cache for 10 minutes. Note: private and s-maxage are contradictory per RFC 7234 — private instructs shared caches not to store the response, so combining them is incorrect. Use public, max-age=0 to suppress browser caching while allowing CDN caching.


6. Practical Examples

Product catalog with CDN caching

HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: public, max-age=300, s-maxage=3600
ETag: "catalog-v42"
Vary: Accept-Encoding

[{ "id": 1, "name": "Widget", "price": 9.99 }, ...]
  • Browsers cache for 5 minutes
  • CDN caches for 1 hour
  • Both support revalidation via ETag

User profile endpoint (private data)

HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: private, max-age=60
ETag: "user-99-v3"

{ "id": 99, "name": "Alice", "email": "alice@example.com" }
  • Only the user's browser caches this
  • CDN is excluded via private
  • Revalidation supported via ETag

Sensitive endpoint — never cache

HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store, no-cache

{ "access_token": "eyJhbGci..." }

Conditional request flow

Client → GET /feed  (first request)
Server → 200 OK, ETag: "feed-v7"

Client → GET /feed, If-None-Match: "feed-v7"  (second request)
Server → 304 Not Modified  (if unchanged — no body transferred)
Server → 200 OK, ETag: "feed-v8"  (if changed — new body + new ETag)