Cache-Aside Pattern

You’ve probably heard people say “just add a cache” when an app feels slow. Okay, but what does that actually look like in the code?

  • Most of the time, when an app uses a cache, it’s using this one pattern.
  • It’s called cache-aside, and it’s the default way apps talk to a cache like Redis or Memcached.
  • The idea is simple once you see it: look in the cache first, and only go to the database if it’s not there.

So let’s build it up step by step. By the end, you’ll be able to explain exactly how it works and when to use it, which is a classic interview question.

🎯 The Idea

Let’s start with the words, because the names hide what’s really going on.

  • A cache is a small, fast store that keeps copies of data so you don’t have to fetch it the slow way every time. Think of it as a notepad next to your desk where you jot down answers you’ll need again.
  • The database (the DB) is your real, permanent store of data. It’s reliable but slower to read from, especially under heavy load.
  • Cache-aside means the cache sits “to the side” of your main flow. Your application code, not the cache, is in charge of using it.

So here’s the pattern in one line. Your app looks in the cache first, and if the data isn’t there, it loads it from the database and saves a copy in the cache for next time.

  • This is also called lazy loading, because data only gets loaded into the cache when someone actually asks for it.
  • Nothing gets cached ahead of time. The cache fills up slowly, on demand, as real requests come in.

Who's in charge here

The key thing to hold onto: in cache-aside, the application manages the cache. The cache itself is dumb. It just stores and returns whatever your code tells it to. Your code does the deciding.

⚙️ How It Works

Let’s walk through a read, step by step. Say your app needs a user’s profile, and that user has the ID 42.

  • First, your app asks the cache: “do you have user 42?”
  • If the cache has it, that’s a cache hit. The app takes the data and returns it right away. Done. Fast.
  • If the cache doesn’t have it, that’s a cache miss. Now the app has to do the slow path.
  • On a miss, the app reads user 42 from the database.
  • Then, before returning, the app writes that data into the cache. So next time someone asks for user 42, it’ll be a hit.
  • Finally, the app returns the data to whoever asked.

Notice who’s doing the work here. The app reads from the DB and the app writes to the cache. The cache never talks to the database on its own.

Hit

Miss

App needs data

Check cache

Found it?

Return data

Read from database

Write data into cache

So the first time you ask for something, it’s slow, because it goes all the way to the database. But every time after that, it’s fast, because it comes straight from the cache.

✍️ What About Writes

Reads are only half the story. What happens when data changes? Say user 42 updates their email. Here’s the thing you have to get right:

  • You update the database. That’s the source of truth, so it always has to be correct.
  • But now the cache still holds the old email. If you leave it, your app will keep handing out stale data.
  • Stale data just means an old copy that no longer matches the real data. It’s the number one bug with caching.

So on a write, you do one of two things along with updating the DB:

  • Invalidate the cache entry, which means delete it. Next time someone reads user 42, it’ll be a miss, and the app will reload the fresh value from the DB. This is the simplest and most common choice.
  • Or update the cache entry directly with the new value, so the next read is still a hit. This is a little faster but easier to get wrong.

Update the DB first, then the cache

The safe order is: write to the database, then deal with the cache. The database is your source of truth, so you never want the cache holding a value that the database never accepted. When in doubt, just delete the cache entry and let the next read refill it.

Cache-aside is the go-to pattern for good reasons. Let’s see why so many systems reach for it.

  • Only the data people actually want gets cached. You never waste memory on things nobody requests, because the cache fills up lazily, only on real misses.
  • A cache failure doesn’t break your reads. If the cache is down, every request just becomes a miss, and your app falls back to the database. It gets slower, but it still works.
  • It’s easy to reason about. The logic lives right there in your application code: check cache, on miss read DB and store. You can read it and understand it.

That last point matters more than it sounds. Because the cache is “to the side,” you can add it to an existing app without rewriting how the app talks to its database.

⚠️ The Downsides

No pattern is free. Cache-aside has two rough edges you should know before an interviewer asks.

  • The first request for any key is always a miss. A fresh, empty cache is called a cold cache, and that first slow read is the price of lazy loading. Until a key gets requested once, it’s never in the cache.
  • You can serve stale data if you forget to invalidate. Remember, the app is responsible for clearing the cache on writes. Miss one write path, and the cache quietly keeps handing out an old value. This is the bug that bites people the most.

Warming the cache

That cold-start problem has a fix. Some teams pre-load popular keys into the cache ahead of time, before real traffic hits. That’s called cache warming, and it’s a handy trick when you know which data everyone is going to ask for first.

🔀 Cache-Aside vs Read-Through

People mix these two up all the time, so let’s draw a clean line. They both speed up reads, but the difference is about who loads the data on a miss.

  • In cache-aside, the application does the loading. On a miss, your code reads the database and writes the value back into the cache. The cache is just a store.
  • In read-through, the cache itself does the loading. On a miss, the cache reaches out to the database, fetches the value, stores it, and hands it back to your app. Your app only ever talks to the cache.

So with read-through, the loading logic lives inside the cache layer or library, not in your app code. Your app stays simpler, but you give up some control, and you need a cache that supports it.

Cache-Aside Read-Through
The app loads from the DB on a miss The cache loads from the DB on a miss
Loading logic lives in your code Loading logic lives in the cache layer
Works with any plain cache Needs a cache that supports it
More control, more code Simpler app, less control

Here’s a simple pros-and-cons view of cache-aside on its own.

Pros Cons
Only requested data gets cached First read per key is always a miss (cold)
Cache failure doesn’t break reads Risk of stale data if invalidation is missed
Simple to understand and add to an app Write logic lives in every place you update data

⚠️ Common Mistakes and Misconceptions

A few ideas trip people up the first time. Let’s clear them out.

  • “The cache updates itself.” No. In cache-aside, the cache does nothing on its own. Your application code has to read the DB on a miss and write the value back. The cache just sits there and stores.
  • “I only need to handle reads.” This is the big one. If you cache on reads but forget to invalidate on writes, you’ll serve stale data. Every place that changes the data must also clear or update the cache.
  • “Check the database, then cache it.” Wrong order. You check the cache first. Only on a miss do you go to the database. If you hit the DB every time, the cache isn’t saving you anything.

🛠️ Design Challenge

Try this on your own to test yourself.

Imagine you’re building a product page for an online store, and you want to cache each product by its ID. Walk through the design:

  • Write the read flow. What does your code do on a hit? On a miss?
  • Now a price changes. What do you do to the cache so customers don’t see the old price?
  • The cache server restarts and loses everything. What happens to your reads, and is the app still correct?

If you can answer all three out loud, you understand cache-aside well enough for an interview.

🧩 What You’ve Learned

You can now explain the most common way apps use a cache. Here’s what you’ve picked up.

  • ✅ Cache-aside means the application checks the cache first and loads from the DB only on a miss.
  • ✅ It’s also called lazy loading, because data is cached on demand, not ahead of time.
  • ✅ On a miss, the app reads the database and writes the value back into the cache.
  • ✅ On a write, you update the database and then invalidate or update the cache entry.
  • ✅ It’s popular because only requested data gets cached and a cache failure doesn’t break reads.
  • ✅ The downsides are cold-start misses and stale data if you forget to invalidate.
  • ✅ Read-through differs because the cache, not the app, loads from the DB on a miss.

Check Your Knowledge

Test what you learned. Pick an answer for each question, then click Check.

  1. 1

    In the cache-aside pattern, what does the app do first on a read?

    Why: The app checks the cache first, and only goes to the database on a miss.

  2. 2

    On a cache miss with cache-aside, who loads the data?

    Why: In cache-aside the application does the loading, reading the database and storing the value in the cache for next time.

  3. 3

    How should you handle a write with cache-aside?

    Why: You update the database first, then delete or update the cache entry so the next read serves a fresh value.

  4. 4

    What happens if the cache goes down with cache-aside?

    Why: With cache-aside a cache failure just turns reads into misses that fall back to the database, so the app keeps working.

🚀 What’s Next?

You’ve got the default caching pattern down. Next, look at how writes can be handled differently and where caching fits in the bigger picture.

Once you’ve got those, you’ll be able to reason about caching choices like a backend engineer.

Share & Connect