DEV Community

Cover image for 𝗖𝗮𝗰𝗵𝗶𝗻𝗴 𝗦𝘁𝗿𝗮𝘁𝗲𝗴𝗶𝗲𝘀 𝗘𝘅𝗽𝗹𝗮𝗶𝗻𝗲𝗱 (Backend & Frontend Developers)
Kiran
Kiran

Posted on

𝗖𝗮𝗰𝗵𝗶𝗻𝗴 𝗦𝘁𝗿𝗮𝘁𝗲𝗴𝗶𝗲𝘀 𝗘𝘅𝗽𝗹𝗮𝗶𝗻𝗲𝗱 (Backend & Frontend Developers)

An interviewer asked: "What caching strategy does your app use?"
The candidate said: "We use Redis."
Interviewer: "That's a tool. I asked for a strategy."
Silence. Interview over. 😶

Here's every caching strategy broken down 👇


🧠 𝗪𝗵𝗮𝘁 𝗶𝘀 𝗖𝗮𝗰𝗵𝗶𝗻𝗴?

👉 Storing data temporarily so future requests are served faster
👉 Avoids hitting the original source — DB, API, server — every time
👉 Wrong strategy = stale data, crashes, data loss

Without cache: User → Server → Database (slow 🐢)
With cache:    User → Cache (fast ⚡)
                        ↓ miss only
                      Database
Enter fullscreen mode Exit fullscreen mode

✔ Reduces latency
✔ Reduces database load
✔ Scales better under traffic


⚡ 1️⃣ 𝗖𝗮𝗰𝗵𝗲-𝗔𝘀𝗶𝗱𝗲 (𝗟𝗮𝘇𝘆 𝗟𝗼𝗮𝗱𝗶𝗻𝗴)

👉 App checks cache first. Miss → fetch DB → store in cache.

async function getUser(id) {
  // Step 1 — check cache
  const cached = await redis.get(`user:${id}`);
  if (cached) return JSON.parse(cached);

  // Step 2 — cache miss, hit DB
  const user = await db.findUser(id);

  // Step 3 — store in cache for next time
  await redis.set(`user:${id}`, JSON.stringify(user), 'EX', 3600);
  return user;
}
Enter fullscreen mode Exit fullscreen mode

✔ Only caches what's actually requested
✔ Cache failure doesn't break the app
❌ First request always slow — cold cache
❌ Risk of stale data between TTL cycles


🎯 2️⃣ 𝗪𝗿𝗶𝘁𝗲-𝗧𝗵𝗿𝗼𝘂𝗴𝗵

👉 Every write goes to cache AND database simultaneously

async function updateUser(id, data) {
  // Write to DB and cache together
  await db.updateUser(id, data);
  await redis.set(`user:${id}`, JSON.stringify(data), 'EX', 3600);
}
Enter fullscreen mode Exit fullscreen mode

✔ Cache always in sync with DB
✔ No stale reads after writes
❌ Write latency increases — two writes every time
❌ Cache fills with data that may never be read

👉 Best for: Read-heavy apps where freshness is critical


🚀 3️⃣ 𝗪𝗿𝗶𝘁𝗲-𝗕𝗲𝗵𝗶𝗻𝗱 (𝗪𝗿𝗶𝘁𝗲-𝗕𝗮𝗰𝗸)

👉 Write to cache instantly. Sync to DB asynchronously later.

User writes → Cache ✅ (instant response)
                 ↓ async worker (batched every 5s)
              Database 🕐 (eventually consistent)
Enter fullscreen mode Exit fullscreen mode
async function updateScore(userId, score) {
  // Instant write to cache
  await redis.set(`score:${userId}`, score);

  // Add to queue — worker syncs to DB later
  await queue.add('syncScore', { userId, score });
}
Enter fullscreen mode Exit fullscreen mode

✔ Blazing fast write performance
✔ Reduces DB write load — batch updates
❌ Risk of data loss if cache crashes before sync
❌ Complex to implement correctly

👉 Best for: Leaderboards, analytics, gaming, counters


🔄 4️⃣ 𝗥𝗲𝗮𝗱-𝗧𝗵𝗿𝗼𝘂𝗴𝗵

👉 App only talks to cache. Cache fetches from DB on miss.

App → Cache → (hit)  → App ✅
App → Cache → (miss) → DB → Cache populates → App ✅
Enter fullscreen mode Exit fullscreen mode
// App never touches DB directly
const user = await cacheProvider.get(`user:${id}`);
// Cache provider handles DB fetch internally on miss
Enter fullscreen mode Exit fullscreen mode

✔ App logic stays clean — zero cache handling code
✔ Cache always populated after first request
❌ Cache provider must support read-through natively
❌ First request latency still exists

👉 Best for: Managed caches — AWS ElastiCache, DAX


⏰ 5️⃣ 𝗥𝗲𝗳𝗿𝗲𝘀𝗵-𝗔𝗵𝗲𝗮𝗱

👉 Cache proactively refreshes data before TTL expires

TTL = 60s
At 45s → background job pre-fetches fresh data
At 60s → cache already has new data ✅ zero miss latency
Enter fullscreen mode Exit fullscreen mode
async function getWithRefreshAhead(key, fetchFn, ttl = 60) {
  const cached = await redis.get(key);
  const ttlRemaining = await redis.ttl(key);

  // Refresh when 75% of TTL has passed
  if (ttlRemaining < ttl * 0.25) {
    fetchFn().then(data =>
      redis.set(key, JSON.stringify(data), 'EX', ttl)
    );
  }

  return cached ? JSON.parse(cached) : fetchFn();
}
Enter fullscreen mode Exit fullscreen mode

✔ No latency spikes on cache expiry
✔ Always serving warm data
❌ May refresh data that's never requested — wasted compute

👉 Best for: Homepages, dashboards, trending feeds


🔑 6️⃣ 𝗖𝗮𝗰𝗵𝗲 𝗜𝗻𝘃𝗮𝗹𝗶𝗱𝗮𝘁𝗶𝗼𝗻 𝗦𝘁𝗿𝗮𝘁𝗲𝗴𝗶𝗲𝘀

👉 Knowing WHEN to clear cache is as important as caching itself

// TTL — expire after fixed time
await redis.set('user:1', data, 'EX', 3600); // expires in 1hr

// Event-based — clear on data change
async function updateUser(id, data) {
  await db.updateUser(id, data);
  await redis.del(`user:${id}`); // invalidate immediately
}

// Cache versioning — bump version on deploy
const key = `user:${id}:v2`; // old v1 cache naturally expires
Enter fullscreen mode Exit fullscreen mode

✔ TTL — simple, automatic
✔ Event-based — precise, immediate
✔ Versioning — safe for deployments


🚨 𝗖𝗮𝗰𝗵𝗲 𝗣𝗿𝗼𝗯𝗹𝗲𝗺𝘀 𝗬𝗼𝘂 𝗠𝘂𝘀𝘁 𝗞𝗻𝗼𝘄

// Cache Stampede — TTL expires, 1000 users hit DB together
// Fix: mutex lock — only one request rebuilds cache
const lock = await redis.set('lock:user:1', 1, 'NX', 'EX', 5);
if (lock) { /* fetch DB and repopulate */ }
else { /* wait and retry */ }

// Cache Penetration — requests for non-existent data bypass cache
// Fix: cache null values too
await redis.set(`user:${id}`, 'NULL', 'EX', 60);

// Cache Avalanche — all keys expire at same time
// Fix: add random jitter to TTL
const ttl = 3600 + Math.floor(Math.random() * 300);
Enter fullscreen mode Exit fullscreen mode

⚛️ 𝗙𝗿𝗼𝗻𝘁𝗲𝗻𝗱 𝗖𝗮𝗰𝗵𝗶𝗻𝗴 (𝗥𝗲𝗮𝗰𝘁)

// React Query — cache-aside in the frontend
const { data } = useQuery({
  queryKey: ['user', id],
  queryFn: () => fetch(`/api/user/${id}`),
  staleTime: 5 * 60 * 1000,   // fresh for 5 mins
  cacheTime: 10 * 60 * 1000,  // keep in memory for 10 mins
});
Enter fullscreen mode Exit fullscreen mode

staleTime — how long data is considered fresh
cacheTime — how long unused data stays in memory
✔ React Query implements cache-aside pattern automatically


🚨 𝗖𝗼𝗺𝗺𝗼𝗻 𝗠𝗶𝘀𝘁𝗮𝗸𝗲𝘀

❌ Caching without a TTL — stale data lives forever
❌ Not handling cache miss gracefully — app crashes
❌ Caching user-specific data globally — data leaks
❌ Using Write-Behind without a reliable queue
❌ Ignoring cache stampede on high-traffic TTL expiry


💡 𝗦𝗲𝗻𝗶𝗼𝗿-𝗟𝗲𝘃𝗲𝗹 𝗜𝗻𝘀𝗶𝗴𝗵𝘁

There are only two hard problems in computer science: cache invalidation and naming things.
Choosing the wrong strategy doesn't slow your app — it silently corrupts it.


🎯 𝗜𝗻𝘁𝗲𝗿𝘃𝗶𝗲𝘄 𝗢𝗻𝗲-𝗟𝗶𝗻𝗲𝗿

Caching strategy is a tradeoff between consistency, latency, and complexity — Cache-Aside for flexibility, Write-Through for consistency, Write-Behind for write performance, Read-Through for clean app logic, and Refresh-Ahead for zero miss latency — with the right choice always depending on your read/write ratio, consistency requirements, and failure tolerance.


#SystemDesign #Backend #Caching #Redis #WebDevelopment #InterviewPrep #SoftwareEngineering #PerformanceOptimization #EngineeringMindset

Top comments (0)