System Design (50 Qs)•Part 1: Foundational Distributed Systems Concepts & Trade-offs (1 - 10)
Caching Strategies: Cache-Aside, Write-Through, Write-Behind, and Write-Around.
In Simple Words: Caching reduces latency and offloads database IOPS by storing hot data in ultra-fast RAM (Redis/Memcached). The choice of caching pattern defines how reads and writes interact:
| Pattern | Read Path | Write Path | Pros | Cons |
|---|---|---|---|---|
| Cache-Aside (Lazy Loading) | App checks cache → on miss reads DB → sets cache | App writes directly to DB → evicts/invalidates cache | Resilient to cache failure; only requested data cached | Cache miss penalty ($3 \times$ network hops); potential stale data |
| Write-Through | App reads directly from cache | App writes to Cache → Cache synchronously writes to DB | Zero stale data; reads are always fast | Write latency penalty (waits for 2 network hops); unused data cached |
| Write-Behind (Write-Back) | App reads from cache | App writes to Cache → Cache asynchronously queues write to DB | Ultra-fast writes; batches DB writes | Risk of data loss if cache crashes before flushing to DB |
| Write-Around | App checks cache | App writes directly to DB (skips cache) | Cache isn't flooded with writes that are never read | Read miss penalty immediately after write |
typescript
// Production Cache-Aside Pattern with Probabilistic Early Expiration (XFetch)
export async function cacheAsideGet<T>(
key: string,
fetchFromDb: () => Promise<T>,
redis: any,
ttlSeconds: number = 3600
): Promise<T> {
const cached = await redis.get(key);
if (cached) {
return JSON.parse(cached);
}
const freshData = await fetchFromDb();
if (freshData !== null && freshData !== undefined) {
await redis.set(key, JSON.stringify(freshData), 'EX', ttlSeconds);
}
return freshData;
}