Skip to main content

Caching Strategies Explained: Cache-Aside vs Write-Through vs Write-Back

Updated 4 min readMarkdown
Caching Strategies Explained: Cache-Aside vs Write-Through vs Write-Back

Add a cache is the most common answer in a system design interview. It is also the answer that gets the most follow-up questions. Where does the data go first? What happens when the cache and the database disagree? What if the cache node dies mid-write?

If you can only say "we'll use Redis", you will get stuck on the first follow-up. This post breaks down the three caching strategies you need to know for caching strategies in system design interviews, with a short code example for each and a simple way to pick between them.

Why the strategy matters more than the tool

Redis and Memcached are tools. A caching strategy is the rule that decides who reads and writes the cache and the database, and in what order. Two teams can use the same Redis cluster and get completely different consistency and performance, just because of this rule.

1. Cache-Aside (Lazy Loading)

The application owns the logic. It checks the cache first. On a miss, it reads from the database, then fills the cache.

def get_user(user_id):
    key = f"user:{user_id}"
    user = cache.get(key)
    if user:                      # cache hit
        return user

    user = db.query("SELECT * FROM users WHERE id = %s", user_id)
    cache.set(key, user, ttl=300) # fill the cache for next time
    return user

def update_user(user_id, data):
    db.update("users", user_id, data)
    cache.delete(f"user:{user_id}")  # invalidate, don't update

Good for: read-heavy workloads such as product pages, profiles and feeds.

Watch out for: the first request after a miss is slow, and there is a small window where stale data can be served. Deleting the key on write (instead of updating it) avoids most race conditions.

2. Write-Through

Every write goes to the cache and the database together, as one operation. The cache is always up to date.

def update_profile(user_id, data):
    db.update("users", user_id, data)           # 1. persist
    cache.set(f"user:{user_id}", data, ttl=300) # 2. keep cache in sync
    return data

Good for: data that is read right after it is written, where stale reads are not acceptable (account settings, inventory counts).

Watch out for: writes are slower because you wait for both. You may also cache data that is never read again, which wastes memory.

3. Write-Back (Write-Behind)

The application writes only to the cache. The cache flushes to the database later, in batches or after a delay.

def record_view(video_id):
    cache.incr(f"views:{video_id}")   # fast, in-memory

# background job, runs every few seconds
def flush_views():
    for key in cache.scan("views:*"):
        count = cache.getdel(key)
        db.execute(
            "UPDATE videos SET views = views + %s WHERE id = %s",
            count,
            key.split(":")[1]
        )

Good for: write-heavy data where small loss is acceptable, such as view counters, likes and analytics events.

Watch out for: if the cache node crashes before the flush, you lose data. Never use this for payments or orders unless the cache is replicated and persistent.

Quick comparison

Strategy Read speed Write speed Consistency Data loss risk
Cache-aside Fast after warm-up Normal Eventual Low
Write-through Fast Slower Strong Low
Write-back Fast Very fast Eventual Higher

How to pick one in an interview

  • Mostly reads, some staleness is fine? Cache-aside.
  • Must read your own writes immediately? Write-through.
  • Huge write volume, can tolerate small loss? Write-back.

Then say out loud what you are trading away. Interviewers reward that more than the choice itself.

Don't forget eviction and TTL

Whatever strategy you choose, the cache has limited memory. Mention an eviction policy (LRU is the usual default) and a TTL so stale entries expire on their own. These two lines often separate a good answer from an average one.

Practice it, don't just read it

Reading about caching is the easy part. The real test is designing it under time pressure and defending your choices. If you want system design practice with hands-on labs instead of passive reading, ScaleDojo has 220+ labs covering HLD, LLD, API design and GenAI/RAG systems, so you can try these trade-offs on real problems.

Key takeaway: the strategy decides your consistency, speed and failure behaviour. Pick it based on the read/write pattern, and always state the trade-off.

6 views

No comments yet. Be the first to comment.