CMD Guide
HomeSystem DesignSystem Design Trade-offs

ServerSide Caching vs ClientSide Caching

Server-side caching and client-side caching both store data temporarily so an application answers requests faster and moves less data over the network. The distinction is where the copy lives and who controls its lifetime. Getting this split right is one of the highest-leverage performance decisions you make, because the two caches sit on opposite ends of the same request and solve genuinely different problems.

The core distinction

A production system almost always uses both, layered. The request below descends through a private browser cache, a shared CDN edge, and finally the origin. Each layer that answers a request removes work and latency from every layer behind it.

diagram
diagram

The response headers that drive both caches

HTTP caching is a contract the origin declares in response headers; every cache in the path obeys it. Four directives carry most of the weight:

The key mechanical rule to internalize: a cache only forwards a request when its own copy is stale. A fresh shared cache answers the conditional request from its own stored copy and never contacts the origin. The timeline and trace below make this concrete for one object served with public, max-age=30, s-maxage=300.

diagram
diagram

Worked trace: one object, four moments

Same URL /product/42, served with Cache-Control: public, max-age=30, s-maxage=300 and ETag: "v7". Assume the browser↔edge round trip is about 20 ms and the edge↔origin round trip about 180 ms (so a full browser↔origin trip is about 200 ms).

TimeBrowser cache (max-age=30)CDN edge (s-maxage=300)Reaches origin?CostBody
t=0 (cold)Miss → forwardMiss → fetch origin, then storeYes, full render~200 ms40 KB
t=10 sFresh (age 10 < 30) → local hitNot contactedNo~1 ms0 (from disk)
t=50 sStale (age 50 > 30) → conditional If-None-Match: "v7" to edgeFresh (age 50 < 300) → answers 304 from its own copyNo~20 ms (edge hop)0 (304)
t=340 sStale → conditional to edgeStale (age 340 > 300) → revalidates to origin with If-None-Match: "v7"Yes, origin replies 304 (unchanged)~200 ms (full round trip)0 (304)

What each row teaches

The lesson: browsers watch max-age, shared caches watch s-maxage, and a 304 is only cheap in wall-clock time when a nearby cache is still fresh enough to answer it.

Server-side caching in depth

Server-side caching keeps one shared copy inside your infrastructure, in front of the expensive work — a database query, a template render, or a downstream API call. The dominant read pattern is cache-aside (lazy loading): the application checks the cache, and on a miss it loads from the source of truth and populates the cache with a TTL.

Cache-aside in Go (go-redis v9)

// import ("context"; "database/sql"; "encoding/json"; "fmt"; "time"
//         "github.com/redis/go-redis/v9")

func getProduct(ctx context.Context, rdb *redis.Client, db *sql.DB, id int) (Product, error) {
	key := fmt.Sprintf("product:%d", id)

	// 1) Try the cache first.
	b, err := rdb.Get(ctx, key).Bytes()
	if err == nil {
		var p Product
		if jerr := json.Unmarshal(b, &p); jerr == nil {
			return p, nil // cache hit
		}
		// corrupt entry: fall through and reload
	} else if err != redis.Nil {
		return Product{}, err // a real Redis error, not just a miss
	}

	// 2) Miss (redis.Nil): read the source of truth.
	p, err := loadProductFromDB(ctx, db, id)
	if err != nil {
		return Product{}, err
	}

	// 3) Best-effort populate for next time, with a TTL.
	if data, jerr := json.Marshal(p); jerr == nil {
		_ = rdb.Set(ctx, key, data, 5*time.Minute).Err()
	}
	return p, nil
}

These are real go-redis v9 signatures: Get(ctx, key) returns a *StringCmd whose .Bytes() yields ([]byte, error); a miss surfaces as the sentinel redis.Nil (which must be distinguished from a genuine error); and Set(ctx, key, value, expiration) returns a *StatusCmd exposing .Err().

The same pattern in Java (Jedis)

public Product getProduct(long id) throws Exception {
	String key = "product:" + id;

	// 1) Try the cache; Jedis#get returns null on a miss.
	String cached = jedis.get(key);
	if (cached != null) {
		return objectMapper.readValue(cached, Product.class);
	}

	// 2) Miss: load from the database.
	Product p = productRepository.findById(id);

	// 3) Populate with a TTL of 300 seconds.
	jedis.setex(key, 300, objectMapper.writeValueAsString(p));
	return p;
}

Common server-side layers: object/query caches (Redis, Memcached), full-page or fragment caches (Varnish, application-level), and CDN edge caches (which are themselves shared server-side caches you configure via response headers).

Pros: faster responses and, more importantly, far less load on the database and backend; one warmed entry serves every user. Cons: extra memory/infrastructure to run and monitor, and you own the hard problem of invalidation — a shared stale entry is wrong for everyone at once.

Client-side caching in depth

Client-side caching stores data on each user's own device, so a repeat read costs zero network. It is controlled by the client, with the server only influencing it through headers.

Pros: the lowest possible latency (no round trip at all), reduced bandwidth, and genuine offline access. Cons: limited by device storage; per user, so it never offloads your database the way a shared cache does; and you cannot force eviction — once you have handed out a copy with a long max-age, you must wait it out or change the URL. This is exactly why fingerprinted asset filenames exist.

Choosing between them (the judgment layer)

DimensionServer-side cacheClient-side cache
LocationYour infrastructure (Redis, Varnish, CDN edge)The user's device (browser, app storage)
ScopeShared across all usersPer individual user
Removes the network round trip?No — reduces it (nearer/cheaper hop)Yes — a local hit costs zero network
Offloads the database?Yes, stronglyOnly for repeat reads by the same user
Works offline?NoYes
Invalidation controlDirect and immediateIndirect (headers) — cannot force eviction
Good forExpensive shared computation, hot rows, personalized-but-reused dataStatic assets, per-user UI state, offline data

When to reach for which

Trade-offs versus the obvious alternatives

Pitfalls and invalidation

Sources

🤖 Don't fully get this? Learn it with Claude

Stuck on ServerSide Caching vs ClientSide Caching? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.

🎨 Explain it visually

Build the mental picture, not memorization.

I just read a lesson on **ServerSide Caching vs ClientSide Caching** (System Design) and want to truly understand it. Explain ServerSide Caching vs ClientSide Caching from first principles using ONE vivid real-world analogy and a visual mental model — draw it as ASCII art or a clear step-by-step diagram — with a concrete example using real numbers. Then ask me one question to check I got the mental picture, and wait for my reply. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🤔 Walk me through it (interactive)

Socratic — adapts to where you're stuck.

Teach me **ServerSide Caching vs ClientSide Caching** interactively. Ask me ONE guiding question at a time, wait for my answer, and adapt to my confusion — build the idea with me step by step instead of explaining it all at once. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧪 Quiz me & fix my gaps

Active recall exposes what you missed.

Quiz me on **ServerSide Caching vs ClientSide Caching** with 5 questions, easy to tricky, ONE at a time. Tell me if each answer is right; at the end, explain clearly what I got wrong and why. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧠 Make it stick

Intuition + hook + flashcards for long-term memory.

Help me remember **ServerSide Caching vs ClientSide Caching** for the long term: give the one-sentence intuition, a memorable hook/mnemonic, a tiny worked example, and 3 active-recall flashcards (Q -> A). If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.

📝 My notes