█
LastWrite
  • > Curriculum
  • > Pricing
  • > For Educators
  • > About
  • > Contact
Log InGet Started

Questions, concerns, bug reports, or suggestions? We read every message, write to us at [email protected].

More ways to reach us →
LastWrite

Structured computer science lessons for aspiring developers and security professionals.

[email protected]

(201) 785-7951

Mon–Fri, 9 AM–5 PM EST

Learn

  • Curriculum
  • Pricing

Company

  • About
  • For Educators & Schools
  • Contact Us

Legal

  • Terms of Service
  • Privacy Policy
© 2026 LastWrite. All rights reserved.
Curriculum/Software Engineering/System Design/Caching
40 minIntermediate

Caching

After this lesson, you will be able to: Apply Redis caching, cache-aside vs write-through patterns, CDN caching, and design a coherent invalidation strategy.

Caching is the cheapest way to multiply system capacity. It's also the source of the most subtle bugs. This lesson covers the patterns that work and the invalidation question that hides in every cache.

Prerequisites:Databases at Scale

The cache layers in a typical web app

Browser cache: HTTP headers (Cache-Control, ETag) control what the client keeps. CDN cache: Cloudflare / Vercel / Fastly serve static and cacheable HTTP responses from edge nodes. Application cache: in-process (Node memory) for hot data inside one instance. Distributed cache: Redis / Memcached, shared across instances. Database cache: Postgres's own buffer pool, materialized views. Each layer is closer to the user (faster) but harder to invalidate. Use them in order of need.

Cache-aside vs write-through vs write-behind

Cache-aside (the default): app checks cache first; on miss, reads DB and populates cache. On write, invalidates cache. Simple, well-understood. Write-through: app writes to cache + DB synchronously. Cache stays consistent at the cost of write latency. Write-behind (write-back): app writes to cache; cache flushes to DB asynchronously. Fastest writes but loses data on cache crash. Most apps should default to cache-aside.

Cache-aside pattern in TypeScript + Redis

Standard pattern; should fit in any service.

python
import { redis } from "@/lib/redis";
import { db } from "@/lib/db";
const TTL_SECONDS = 60 * 5; // 5 min
export async function getUser(id: string) {
const cacheKey = `user:${id}`;
// 1. Try cache
const cached = await redis.get(cacheKey);
if (cached) return JSON.parse(cached);
// 2. On miss, load from DB
const user = await db.user.findUnique({ where: { id } });
if (!user) return null;
// 3. Populate cache (don't await; fire-and-forget is fine for reads)
redis.set(cacheKey, JSON.stringify(user), "EX", TTL_SECONDS).catch(() => {});
return user;
}
export async function updateUser(id: string, data: Partial<User>) {
// 1. Write to DB
const updated = await db.user.update({ where: { id }, data });
// 2. Invalidate cache (delete, don't write, write-through has subtle bugs)
await redis.del(`user:${id}`);
return updated;
}

TTL vs explicit invalidation

TTL (Time To Live): the cache entry auto-expires after N seconds. Simplest invalidation; you accept stale data for up to N seconds. Explicit invalidation: code that writes data also deletes the cache entry. Stays fresh but requires every writer to remember. Combo (the production default): set a TTL as a safety net (e.g. 5 minutes), AND invalidate explicitly on writes. Even if a writer forgets, the cache becomes correct again within 5 minutes.

CDN caching: the highest-leverage layer

If you cache a HTTP response at the CDN edge for 60s, your origin handles 1/60 the load for that endpoint. Cache-Control: public, max-age=60, s-maxage=60 (browser+CDN cache for 60s). Cache-Control: public, max-age=0, s-maxage=600, stale-while-revalidate=60 (browser no cache; CDN cache for 600s; refresh in background after 600s). Modern Next.js + Vercel cache responses by default; opt out with `dynamic = 'force-dynamic'` only when you must. Whenever you can move the cache from Redis to the CDN, you've removed a hop AND saved origin compute.

Common mistakes only experienced engineers avoid

Caching without an invalidation strategy. The cache will return stale data; it's a question of when, not if. Caching everything because 'it's fast'. Every cache adds invalidation complexity; cache the heavy-read, slow-write paths. Using cache-aside for hot keys that get thundering herds (10K concurrent requests on a single cold key all hammer the DB). Use a 'cache stampede' guard (lock, single-flight, or stale-while-revalidate). Setting TTLs in hours when minutes would do. Stale data accumulates. Skipping cache hit-rate metrics. Without them, you can't tell if your cache is helping or just adding cost.

Quick Check

What's the cleanest default cache pattern for a typical web app?

Pick the production-default choice.

Sign in and purchase access to unlock this lesson.

Sign in to purchase
←Databases at Scale
Back to System Design
Load Balancing→