What Is Cache Eviction?

What Is Cache Eviction?

A friendly, deep, and practical walkthrough of how computers decide what to keep and what to throw away — from first principles to how Netflix, Redis, CPUs and your browser handle billions of requests a second.

01

Introduction & History

Imagine you have a small backpack, but you own hundreds of toys. You cannot carry all your toys everywhere, so you pick your favorites — the ones you play with the most — and keep them in the backpack. The rest stay at home in a big toy box. When you get bored of a toy in your backpack, you swap it out for a different one from the toy box.

That simple act of swapping — deciding what stays in your small, fast-to-reach backpack and what goes back into the big, slow-to-reach toy box — is exactly what computers do every single second. This process is called cache eviction.

Simple Analogy

A cache is like your kitchen counter. Your fridge and pantry (the “main storage”) hold everything, but they’re a few steps away. The counter is small but instantly reachable. You keep your most-used spices and utensils on the counter. When the counter gets full and you need space for something new, you must decide what to put away. That decision — what to remove — is eviction.

What is a “cache”?

A cache (pronounced “cash”) is a small, fast storage layer that sits between a slow data source and the user who wants that data. Computers store a copy of frequently used data in this fast layer so that future requests for the same data are served quickly, without going all the way back to the slow, original source.

A short history

The idea of caching is almost as old as computing itself. In the 1960s, engineers building early mainframe computers noticed something interesting: a CPU (the “brain” of the computer) was incredibly fast, but the main memory (RAM) that fed it instructions and data was comparatively slow. The CPU would sit idle, waiting for data. This gap between processor speed and memory speed became known as the memory wall.

In 1965, a computer scientist named Maurice Wilkes proposed adding a small amount of very fast memory close to the CPU to hold recently used data. This was the birth of the hardware cache. Since fast memory is expensive, it could never be as big as main memory — so from day one, engineers had to answer a critical question: “When this small memory is full and I need to store something new, what should I remove?”

That question is the entire subject of this tutorial. As computing grew — from single CPUs, to operating systems, to databases, to the World Wide Web, to globally distributed systems like Netflix and Amazon — the same core problem kept reappearing at every layer: CPU caches, disk caches, browser caches, database query caches, Content Delivery Networks (CDNs), and distributed caching systems like Redis and Memcached. Every one of them needs a strategy for cache eviction.

Evolution of Caching 1965 Wilkes proposes hardware cache 1980s CPU L1 / L2 caches standard 1990s Browsers add page caching 2000s CDNs cache content globally 2009 Memcached & Redis distributed caching 2020s AI-driven adaptive eviction policies The eviction problem has traveled across every layer — from silicon to global edge networks.
Fig 1.1 — Caching moved from CPU hardware to global, distributed software systems, but the eviction problem never went away.

Today, cache eviction is not just a computer science topic — it is one of the most frequently discussed subjects in software engineering interviews and one of the most important concepts for building systems that are fast, cheap to run, and reliable at scale.

02

Problem & Motivation

To understand why eviction matters, we first need to understand why caches exist, and why they can never be infinitely large.

Why do we need caches at all?

Every piece of data in a computer system lives somewhere: a hard disk, a database on another server, or across the internet. Reading data from these places takes time — sometimes a tiny amount of time, sometimes a very noticeable amount.

Storage TypeApproximate Access TimeEveryday Comparison
CPU Register~0.3 nanosecondsBlinking your eye in a thousandth of a second
CPU Cache (L1)~1 nanosecondReaching into your pocket
RAM (Main Memory)~100 nanosecondsWalking to the next room
SSD Disk~100 microsecondsDriving to a nearby store
Network call (same region)~1 millisecondTaking a short flight
Network call (across the world)~150+ millisecondsA long international journey

Notice how dramatic the difference is. If your application had to fetch every single piece of data from a database on the other side of the world every time a user asked for it, your app would feel painfully slow. A cache solves this by keeping a copy of recently or frequently requested data close by — in memory, on the same machine, or at least much closer than the original source.

Why can’t we just cache everything?

This is where the real problem begins. Fast memory (like RAM) is expensive and physically limited. You cannot buy infinite RAM, and even if you could, searching through a gigantic cache would itself become slow. So every cache has a fixed, limited size — a capacity.

!
The Core Problem

A cache is always smaller than the data source it represents. Once the cache is full, and new data needs to be stored, something old must be removed to make room. Deciding what to remove — in a way that keeps the cache useful — is the problem that cache eviction solves.

If we evict the wrong data — say, data that was about to be requested again in the next second — we lose the speed benefit of caching entirely. The application has to go all the way back to the slow source, defeating the purpose. This is why the eviction strategy is just as important as the decision to cache in the first place.

Simple Analogy

Think of a small classroom library shelf that can only hold 10 books, while the school has 10,000 books in the main library. If the teacher randomly throws away a book every time a new one arrives, students may lose their favorite, most-read book. A smart teacher instead removes the book nobody has picked up in months. That “smart removal” is cache eviction in action.

03

Core Concepts

Before diving into algorithms, let’s build a solid vocabulary. Every term below is something you will see again and again in caching discussions and interviews.

Cache Hit and Cache Miss

A cache hit happens when requested data is found in the cache. A cache miss happens when it is not found, forcing the system to fetch it from the slower original source (often called the “origin” or “backing store”).

Beginner Example

You ask your mom, “Where are my socks?” If she instantly points to the drawer (fast answer, no searching), that’s a hit. If she has to go search through boxes in the attic, that’s a miss.

Software Example

When you visit a website you visited yesterday, your browser might already have the images and CSS files stored locally. Loading them from your own hard disk (a hit) is much faster than downloading them again from the internet (a miss).

Cache Capacity

This is the maximum amount of data a cache can hold — measured in number of items, or in bytes/megabytes. Once this limit is reached, the cache is “full,” and adding new data requires eviction.

Cache Eviction

Cache eviction is the process of removing an existing item from a full cache to make room for a new item. The rule that decides which item gets removed is called an eviction policy (also called a replacement policy).

Eviction Policy / Algorithm

This is the “brain” behind eviction — the specific strategy used to pick a victim. Common examples include LRU (Least Recently Used), LFU (Least Frequently Used), and FIFO (First In, First Out). We will explore each in depth in Section 5.

TTL (Time To Live)

TTL is a timer attached to a cached item. Once the timer expires, the item is considered “stale” (outdated) and is either removed automatically or refreshed from the original source on the next request.

Simple Analogy

Milk in your fridge has an expiry date. Even if there’s room in the fridge, you throw away milk once it passes its date because it’s no longer safe or fresh. TTL works the same way for cached data — some data (like today’s weather) becomes “expired” and unreliable after a short time.

Cache Invalidation

Sometimes data in the cache becomes outdated not because time passed, but because the original data changed. Cache invalidation is the act of marking or removing cached data because we know it no longer matches the real, current data. This is different from eviction — eviction happens because of space pressure; invalidation happens because of a correctness problem. There’s a famous saying in computer science:

“There are only two hard things in computer science: cache invalidation and naming things.” — Phil Karlton

Working Set

The working set is the group of data items that a program or system actively needs during a given period of time. A good cache tries to hold the current working set so that most requests become hits.

Hit Ratio (Hit Rate)

This is the percentage of requests served directly from the cache, calculated as:

Formula
Hit Ratio = (Number of Cache Hits) / (Total Requests) × 100

A higher hit ratio generally means the cache — and its eviction policy — is doing a good job.

Cache Hit

Data found instantly in the cache.

Cache Miss

Data not found; fetched from source.

Eviction

Removing old data to make room.

TTL Expiry

Data removed because time ran out.

04

Architecture & Components

Let’s now look inside a cache system to understand what it is actually made of.

1. The Storage Structure

Most caches are built using a combination of two data structures:

  • Hash Map (Hash Table): Gives near-instant (O(1) average time) lookup of a value using its key — like an index at the back of a book that tells you exactly which page to open.
  • Doubly Linked List: Keeps track of the order of items — for example, which item was used most recently or least recently. This ordering is essential for many eviction algorithms.

2. The Eviction Policy Module

This is the decision-making component. It watches how items are accessed and, when the cache is full, tells the storage structure which key to remove.

3. Capacity Manager

Keeps track of current size versus maximum allowed size (either item-count based or memory-size based) and triggers eviction when a new insert would exceed the limit.

4. Expiration Manager (TTL Handler)

Independently of eviction-due-to-space, this component tracks per-item expiry timers and removes or refreshes expired entries — sometimes proactively (a background sweep) and sometimes lazily (checked only when the item is accessed).

5. Metrics Collector

Tracks hits, misses, evictions, and latency so engineers can monitor how well the cache is performing (more in Section 11).

A Typical Cache Request — Check, Hit or Miss, Evict If Needed Client Request Key exists in cache? Return cached value HIT yes no Fetch from Origin / Database Cache full? Eviction policy picks victim & removes it from cache yes no Insert new item → return (MISS)
Fig 4.1 — The architecture of a typical cache request: check, hit or miss, and eviction if the cache is full.

Where caches live in a system

CPU Cache

Hardware-level, stores instructions & data near the processor.

Browser Cache

Stores web page assets on your local device.

Database Cache

Stores results of expensive queries.

CDN Cache

Stores content at edge locations near users worldwide.

Application Cache

In-memory store like Redis or Memcached, used by microservices.

Mobile App Cache

Local storage for offline access and speed.

05

Internal Working & Eviction Algorithms

This is the heart of the tutorial. Let’s go through the major eviction algorithms one by one — what they are, why they exist, how they work internally, and how to implement them.

5.1 FIFO — First In, First Out

What it is: The oldest item added to the cache is the first one removed, regardless of how often or recently it was used.

Simple Analogy

Think of a queue at a bakery — the first person who joined the line is served first, and once served, they leave, no matter how “important” they are.

Why it exists: It’s the simplest possible policy to implement — just a queue.

Downside: It ignores usage patterns. An item that is used constantly might still get evicted just because it was added early.

5.2 LIFO — Last In, First Out

The newest item is evicted first. This is rarely used in general-purpose caching because it can immediately throw away the very data that was just requested, but it has niche uses in specific stack-based processing scenarios.

5.3 LRU — Least Recently Used

What it is: The item that has not been accessed for the longest time is evicted first. This is the most widely used eviction policy in the real world.

Simple Analogy

Imagine your desk can hold only 5 books. Every time you use a book, you place it on top of the pile. When the desk is full and you need space for a new book, you remove the book at the very bottom — the one you haven’t touched in the longest time.

Why it works well: It’s based on the idea of temporal locality — the observation that data used recently is likely to be used again soon. This holds true for a huge number of real-world access patterns (like a shopping website showing recently viewed products).

LRU Cache (capacity 3) — Access Sequence Access: A A Access: B A B Access: C A B C Access A → MRU B C A Access D → evict B C A D Re-accessing an item bumps it to the head; eviction always removes the tail (least recent).
Fig 5.1 — In LRU, re-accessing an item refreshes its position; the least recently touched item gets evicted first.

How LRU is implemented internally

LRU is typically implemented using a combination of a HashMap (for O(1) lookups) and a Doubly Linked List (to maintain usage order). The most recently used item sits at the “head” of the list, and the least recently used item sits at the “tail.” When an item is accessed, it’s unlinked and moved to the head — an O(1) operation. When eviction is needed, the tail item is removed — also O(1).

Java — LRU Cache using LinkedHashMap
import java.util.LinkedHashMap;
import java.util.Map;

public class LRUCache<K, V> extends LinkedHashMap<K, V> {
    private final int capacity;

    public LRUCache(int capacity) {
        // accessOrder = true means the map reorders entries
        // based on access, not just insertion.
        super(capacity, 0.75f, true);
        this.capacity = capacity;
    }

    @Override
    protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
        // Called automatically after every insertion.
        // Returning true tells the map to evict the eldest entry.
        return size() > capacity;
    }

    public static void main(String[] args) {
        LRUCache<Integer, String> cache = new LRUCache<>(3);
        cache.put(1, "A");
        cache.put(2, "B");
        cache.put(3, "C");
        cache.get(1);          // "A" becomes most recently used
        cache.put(4, "D");     // Evicts "B" (least recently used)
        System.out.println(cache.keySet()); // [3, 1, 4]
    }
}

This example uses Java’s built-in LinkedHashMap, which already maintains access order internally, making it an elegant, production-safe way to build a simple LRU cache without writing your own linked list.

5.4 LFU — Least Frequently Used

What it is: The item accessed the fewest number of times is evicted first, regardless of when it was last used.

Simple Analogy

A restaurant keeps track of how many times each dish has been ordered. When the menu needs a new spot, the dish ordered the fewest times gets removed — even if it was ordered once yesterday.

Why it exists: LRU can be tricked by a single, one-time burst of access — an item used once recently might push out something that is used constantly but wasn’t touched in the last few seconds. LFU tries to fix this by counting total usage, favoring consistently popular items.

Downside: LFU has a “new item problem” — a brand-new, genuinely useful item starts with a frequency count of 1 and may be evicted quickly before it has a chance to prove its value. Many production systems solve this using a hybrid design.

How LFU is implemented internally

A naive approach keeps a frequency counter per item and scans for the minimum — but that’s slow (O(n)). Efficient LFU implementations use a HashMap of key → frequency, plus a HashMap of frequency → doubly linked list of keys with that frequency, allowing O(1) get and put operations.

5.5 MRU — Most Recently Used

The exact opposite of LRU — the most recently used item is evicted first. This sounds strange, but it is useful in specific scenarios, such as when a system scans through a large dataset sequentially and is unlikely to reuse an item right after touching it (for example, iterating through a huge file only once).

5.6 Random Replacement (RR)

A randomly chosen item is evicted. It sounds too simple to be useful, but it has one big advantage: extremely low overhead, since there’s no bookkeeping of order or frequency needed. Some CPU hardware caches actually use randomized or pseudo-random eviction because tracking exact LRU order for millions of tiny hardware cache lines is expensive in silicon.

5.7 TTL-Based Eviction (Time-based)

Every cached item is given an expiration time. When that time passes, the item becomes invalid and is evicted, either immediately by a background cleanup process or “lazily” the next time someone tries to read it. This is extremely common for data that becomes stale quickly, like stock prices, weather data, or authentication tokens.

Java — Simple TTL Cache Entry
public class TTLCacheEntry<V> {
    private final V value;
    private final long expiryTimeMillis;

    public TTLCacheEntry(V value, long ttlMillis) {
        this.value = value;
        this.expiryTimeMillis = System.currentTimeMillis() + ttlMillis;
    }

    public boolean isExpired() {
        return System.currentTimeMillis() > expiryTimeMillis;
    }

    public V getValue() {
        return isExpired() ? null : value;
    }
}

5.8 Clock (Second-Chance) Algorithm

What it is: An efficient approximation of LRU, widely used in operating systems for page replacement. Items are arranged in a circular list (like a clock face) with a “reference bit” on each item. When the clock hand passes an item, if its reference bit is 1 (recently used), the bit is reset to 0 and the item gets a “second chance.” If the bit is already 0, the item is evicted.

Clock (Second-Chance) Algorithm A bit=1 B bit=0 C bit=1 D bit=0 bit=1 → reset, keep bit=0 → evict A cheap approximation of LRU: bit=1 gets one more spin around the clock.
Fig 5.2 — The Clock algorithm scans in a circle; items with bit=0 (highlighted) are evicted, items with bit=1 get a second chance and their bit is reset.

Why it exists: True LRU requires updating a list on every single access, which is costly at the scale of operating system memory pages (millions of pages, constant access). Clock gives a “good enough” approximation of LRU with far less overhead.

5.9 ARC — Adaptive Replacement Cache

What it is: A more advanced algorithm (developed at IBM) that dynamically balances between recency (like LRU) and frequency (like LFU), adjusting itself automatically based on the actual access pattern it observes. ARC keeps two lists — one for recently used items and one for frequently used items — and shifts the “boundary” between them based on which strategy has been more successful recently.

Why it exists: Real-world workloads often mix short bursts of one-time reads with long-term hot data. ARC tries to get “the best of both worlds” without requiring engineers to manually tune the cache. Redis’s Least Frequently Used config and databases like PostgreSQL’s buffer cache have drawn inspiration from ARC-like ideas.

5.10 Comparing All Policies

AlgorithmTracksBest ForWeakness
FIFOInsertion orderSimplicity, streaming dataIgnores usage patterns
LRURecencyGeneral-purpose, most workloadsVulnerable to one-time scan floods
LFUFrequencyStable, long-term popularity patternsSlow to adopt new popular items
MRURecency (inverse)Sequential one-pass scansRare, niche use case
RandomNothingHardware caches, ultra-low overheadUnpredictable performance
TTLTimeData with a natural expiryDoesn’t consider usage at all
ClockApproximate recencyOS page replacementLess precise than true LRU
ARCRecency + FrequencyMixed, unpredictable workloadsMore complex to implement
i
Interview Tip

When asked to “design a cache,” the expected answer in most interviews is an LRU cache built with a HashMap + Doubly Linked List, achieving O(1) time for both get and put operations. Be ready to explain why a plain array or single linked list would be too slow (O(n) for finding and moving items).

5.11 Time & Space Complexity Cheat Sheet

Understanding the computational cost of each policy is important, especially for interviews. Here is how the major algorithms compare when implemented efficiently:

AlgorithmGetPut/InsertEvictionExtra Space
FIFOO(1)O(1)O(1)O(n) for the queue
LRUO(1)O(1)O(1)O(n) for hashmap + linked list
LFU (efficient)O(1)O(1)O(1)O(n) for two hashmaps
LFU (naive, heap-based)O(log n)O(log n)O(log n)O(n) for the heap
RandomO(1)O(1)O(1)O(n) for a simple array
ClockO(1)O(1)Amortized O(1)O(n) for the circular buffer

Notice that most production-grade implementations aim for O(1) average time on every operation. This is a strict requirement because a cache is only useful if it is faster than going to the original data source — a slow cache defeats its own purpose.

5.12 Concurrency & Thread Safety

In real applications, many threads (or many separate application processes) read from and write to the same cache at the same time. If two threads try to evict and insert at the same instant without any coordination, the cache’s internal data structures — like the linked list used for LRU — can become corrupted, leading to crashes or silently wrong data.

Simple Analogy

Picture two people trying to rearrange the same bookshelf at the same time, both grabbing and moving books without talking to each other. Books can end up in the wrong place, or even fall and get lost. A cache needs the same kind of “one person moves the shelf at a time” discipline.

There are several common strategies for making a cache safe under concurrent access:

  • Coarse-grained locking: A single lock guards the entire cache. Simple and safe, but every thread must wait its turn, which can become a bottleneck under heavy load.
  • Fine-grained (striped) locking: The cache is split into multiple independent segments, each with its own lock — similar to how Java’s ConcurrentHashMap works internally. Threads working on different segments don’t block each other, greatly improving throughput.
  • Lock-free / CAS-based structures: Using atomic “compare-and-swap” hardware instructions to update shared counters (like access frequency) without traditional locks at all, reducing contention even further.
  • Single-threaded event loop: Some caches (most famously Redis) avoid the problem entirely by processing commands one at a time on a single thread, which removes race conditions by design, at the cost of not using multiple CPU cores for a single instance.
Java — Thread-Safe Cache Wrapper
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.LinkedHashMap;
import java.util.Map;

public class ThreadSafeLRUCache<K, V> {
    private final int capacity;
    private final LinkedHashMap<K, V> store;
    private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    public ThreadSafeLRUCache(int capacity) {
        this.capacity = capacity;
        this.store = new LinkedHashMap<>(capacity, 0.75f, true) {
            @Override
            protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
                return size() > ThreadSafeLRUCache.this.capacity;
            }
        };
    }

    public V get(K key) {
        lock.readLock().lock();
        try {
            return store.get(key);
        } finally {
            lock.readLock().unlock();
        }
    }

    public void put(K key, V value) {
        lock.writeLock().lock();
        try {
            store.put(key, value);
        } finally {
            lock.writeLock().unlock();
        }
    }
}

This example uses a ReentrantReadWriteLock, which allows multiple threads to read simultaneously but ensures only one thread can write (insert or evict) at a time — a common, practical balance between safety and performance.

06

Data Flow & Lifecycle

Let’s trace the complete life of a single cached item, from birth to eviction.

  1. 1
    Request arrives
    A client asks for a piece of data using a key (like a user ID or a product ID).
  2. 2
    Cache lookup
    The system checks whether the key exists in the cache.
  3. 3
    Cache miss handling
    If not found, the system fetches the data from the origin (database, API, disk) — this is naturally slower.
  4. 4
    Cache population
    The fetched data is stored in the cache with metadata like timestamp, TTL, and access count.
  5. 5
    Capacity check
    If the cache is now over capacity, the eviction policy chooses a victim and removes it.
  6. 6
    Repeated access (Hits)
    Future requests for the same key are served instantly from the cache, updating recency/frequency metadata each time.
  7. 7
    Expiration or invalidation
    Eventually the item either expires (TTL), gets invalidated (source data changed), or gets evicted (space pressure) — and the cycle can begin again on the next miss.
Cached Request Lifecycle — Hit vs Miss User Application Cache Database Request product #101 get(“product:101”) alt [cache hit] cached data fast response else [cache miss] not found SELECT * FROM products WHERE id=101 row put(“product:101”) — if full, evict LRU slower first-time response The first request pays the miss penalty; every subsequent request in the window is free.
Fig 6.1 — A typical request lifecycle showing the branch between a cache hit and a cache miss.
07

Advantages, Disadvantages & Trade-offs

Caching with a good eviction policy provides massive benefits, but nothing in engineering is free. Let’s look at both sides honestly.

Advantages
  • Dramatically faster response times for repeated data
  • Reduces load on databases and backend services
  • Lowers infrastructure and bandwidth costs
  • Improves scalability under high traffic
  • Enables offline or low-connectivity experiences
Disadvantages & Trade-offs
  • Risk of serving stale (outdated) data
  • Extra memory cost for the cache layer itself
  • Added system complexity (invalidation, TTL, consistency)
  • Wrong eviction policy can hurt performance (“cache thrashing”)
  • Distributed caches introduce network and consistency challenges

Cache Thrashing

This happens when the cache is too small for the actual working set, causing items to be evicted right before they’re needed again — leading to a constant cycle of misses. It’s like trying to fit 20 people’s lunch into a fridge meant for 5 — nothing stays long enough to be useful.

!
Common Pitfall

Increasing cache size is not always the answer to thrashing. Sometimes the real fix is choosing a smarter eviction policy (like ARC instead of plain LRU) or improving how the application accesses data.

08

Performance & Scalability

The performance of a cache is measured not just by raw speed, but by how well it scales as traffic grows.

Key Performance Metrics

Hit Ratio
% requests served from cache
Latency
Time to retrieve a cached item
Eviction Rate
How often items are removed
Throughput
Requests handled per second

Scaling a cache

A single machine’s memory has limits. To scale caching beyond one server, engineers typically use:

  • Vertical scaling: Adding more RAM to a single cache server (simple, but limited by hardware).
  • Horizontal scaling (sharding): Splitting the cache across multiple servers, where each server holds a portion of the keys, often using consistent hashing to decide which server owns which key.
  • Tiered caching: Combining multiple cache layers — for example, a small, ultra-fast local (in-process) cache, backed by a larger distributed cache like Redis, backed by the database.
Tiered Caching — Small & Fast in Front, Large & Shared Behind Application Servers L1 local in-memory cache L2 Redis Cluster (distributed) Database source of truth miss miss Each layer absorbs a share of the load; the database only sees the coldest tail.
Fig 8.1 — Tiered caching: fast, small local caches absorb the hottest data; a larger shared cache catches the rest before hitting the database.

Consistent Hashing

When a cache is spread across many servers, we need a way to decide which server stores which key — and to avoid re-shuffling almost everything when a server is added or removed. Consistent hashing arranges servers and keys on a virtual circle (“hash ring”), so adding or removing one server only affects a small fraction of the keys nearby it on the ring, not the entire dataset.

09

High Availability & Reliability

In large systems, the cache itself must not become a single point of failure. If your one cache server crashes and every request suddenly floods the database at once, this is called a “cache stampede” or “thundering herd” problem.

Techniques for reliability

  • Replication: Keeping copies of cache data on multiple nodes, so if one fails, others can serve requests.
  • Partitioning (Sharding): Spreading data across nodes so a single node failure only affects part of the cache, not all of it.
  • Request Coalescing: When many requests ask for the same missing key at once, only one request actually goes to the database, and the rest wait for that result — preventing a stampede.
  • Jittered TTLs: Adding small random variation to expiration times so that many items don’t expire at the exact same moment, which would otherwise cause a sudden traffic spike to the database.
  • Graceful degradation: If the cache is down, the system should still function (slower), rather than crashing entirely.

CAP Theorem and Caching

The CAP theorem states that a distributed system can only guarantee two out of three properties at the same time: Consistency (everyone sees the same data), Availability (the system always responds), and Partition Tolerance (the system keeps working despite network failures). Distributed caches typically favor Availability and Partition Tolerance over strict Consistency — meaning it’s usually acceptable for a cache to serve slightly stale data for a short window rather than become unavailable. This is why TTLs and eventual consistency are so common in caching design.

Simple Analogy

Imagine several branches of a library that share updates with each other. If one branch updates a book’s location, it might take a few minutes for other branches to hear about the change. During that short window, someone might get slightly outdated information — but the library stays open and useful the whole time.

Disaster Recovery and Backup

Because a cache is often treated as disposable, rebuildable data, many teams skip backing it up entirely — the database remains the “source of truth,” and the cache can always be rebuilt by replaying real traffic. However, for caches holding computationally expensive results (like machine learning feature stores), losing the entire cache at once can cause a severe, sudden spike in load on the origin system. In these cases, engineers use snapshotting (periodically saving cache contents to disk, as Redis does with its RDB snapshots) or append-only logs (like Redis’s AOF) so the cache can be restored quickly after a crash instead of starting completely cold.

Consensus and Replication

When a distributed cache has multiple copies of the same data across different nodes, the nodes need to agree on which copy is authoritative, especially after a network failure and reconnection. Systems like Redis Sentinel and Redis Cluster use leader-election style consensus (a designated “primary” node accepts writes, and “replica” nodes copy from it) to keep replicas synchronized and to automatically promote a new primary if the original one fails. This is conceptually related to broader distributed consensus algorithms like Raft and Paxos, which are used in many distributed databases to safely agree on a single source of truth across many machines.

10

Security

Caches store real data, which means they can become a security risk if not handled carefully.

Sensitive Data Exposure

Avoid caching passwords, tokens, or personal data in plaintext, especially in shared or logged caches.

Cache Poisoning

Attackers may try to trick a cache into storing malicious or incorrect content that gets served to other users.

Access Control

Distributed caches like Redis should require authentication and run in private networks, not exposed to the public internet.

Denial of Service

Attackers might flood a system with unique, uncacheable requests specifically to force cache misses and overload the origin database.

!
Best Practice

Never cache per-user sensitive data (like another user’s private profile) under a shared or predictable cache key. Always scope sensitive cache keys per authenticated user, and set short TTLs for anything security-related, like session tokens.

11

Monitoring, Logging & Metrics

You cannot improve what you don’t measure. Production caching systems are closely monitored using metrics such as:

MetricWhat It Tells You
Hit RatioHow effective the cache is overall
Eviction CountHow often items are forced out — high numbers may mean the cache is too small
Memory UsageHow close the cache is to its capacity limit
Latency (p50, p95, p99)How fast typical and worst-case requests are served
Key Expiration RateHow often TTL-based removal happens versus capacity-based eviction
Error RateConnection failures, timeouts, or serialization errors

Popular tools for monitoring caches in production include Prometheus + Grafana for metrics dashboards, Redis INFO / SLOWLOG commands for built-in diagnostics, and distributed tracing tools like OpenTelemetry to see exactly how much time a request spends waiting on the cache versus the database.

i
Pro Tip

A sudden drop in hit ratio is often the very first sign of a bigger problem — like a bad deployment invalidating too many keys at once, or a traffic pattern change that the current eviction policy can’t handle well.

12

Deployment & Cloud

Modern applications rarely build caching from scratch. Cloud providers offer managed caching services that handle scaling, replication, and failover automatically:

  • Amazon ElastiCache — managed Redis or Memcached on AWS
  • Google Cloud Memorystore — managed Redis on Google Cloud
  • Azure Cache for Redis — managed Redis on Microsoft Azure
  • Cloudflare / Akamai / Fastly — global CDN caching at the network edge

These managed services take care of infrastructure concerns (patching, scaling, backups) while engineers focus on configuring eviction policy, TTLs, and cache-key design — the parts that actually determine application performance.

Container & Orchestration Considerations

In Kubernetes-based deployments, caches are often deployed as StatefulSets (to preserve stable network identities and storage) rather than regular stateless Deployments, since cache nodes frequently need predictable addressing for clients to find the correct shard.

13

Databases, Caching & Load Balancing

Caching sits closely alongside two other core building blocks of scalable systems: databases and load balancers.

Database Query Caching

Expensive database queries (like complex joins or aggregations) can be cached so repeat requests skip the database entirely. Many databases (like MySQL, PostgreSQL) have internal buffer caches, and applications often add an external layer (Redis) in front for even more control.

Read-Through and Write-Through Caching

PatternHow It WorksTrade-off
Cache-Aside (Lazy Loading)Application checks cache first; on miss, loads from DB and populates cacheSimple, but first request is always slow
Read-ThroughCache itself is responsible for loading missing data from the DBCleaner app code, but tighter coupling to the cache layer
Write-ThroughEvery write goes to the cache and the DB at the same timeAlways fresh data, but slightly slower writes
Write-Behind (Write-Back)Writes go to the cache first, and are asynchronously flushed to the DB laterVery fast writes, but risk of data loss if cache crashes before flush

Load Balancing and Caching

Load balancers distribute incoming traffic across multiple servers. When paired with caching, they can use techniques like sticky sessions (routing the same user to the same server, so local in-memory caches stay useful) or route through a shared distributed cache so any server can serve any user with the same fast performance.

14

APIs & Microservices

In microservice architectures, each service often manages its own local cache, plus a shared distributed cache for data used across multiple services.

HTTP Caching (API Layer)

Web APIs use HTTP headers to control caching behavior at the browser or proxy level:

  • Cache-Control: max-age=3600 — tells clients they can reuse the response for one hour
  • ETag — a fingerprint of the content, used to check “has this changed?” without re-downloading everything
  • Cache-Control: no-store — tells clients never to cache this response, common for sensitive data
Microservices Sharing a Distributed Cache Service A Service B Service C Redis Cluster shared cache layer Central Database on miss One cache serves many services — but key naming must stay disciplined.
Fig 14.1 — Multiple microservices sharing one distributed cache layer, reducing duplicate database load.

Sharing a cache across microservices reduces duplicate work, but requires careful key naming (like service:entity:id) so that services don’t accidentally overwrite each other’s data.

15

Design Patterns & Anti-patterns

Good Patterns

  • Cache-Aside with jittered TTL: A simple, resilient default for most applications.
  • Tiered caching (L1 local + L2 distributed): Balances raw speed with shared consistency.
  • Negative caching: Even caching “this item does not exist” briefly, to avoid repeatedly hitting the database for known-missing keys.
  • Request coalescing / single-flight: Ensures only one request fetches a missing key while others wait, avoiding stampedes.

Anti-patterns to Avoid

!
Caching Everything

Not all data benefits from caching. Rapidly changing data with low reuse (like a live vote counter checked once) can waste cache space and cause more harm (staleness bugs) than good.

!
No Expiration Strategy

Caching data forever without a TTL or invalidation plan is one of the most common real-world bugs, leading to users seeing outdated information indefinitely.

!
Using the Wrong Eviction Policy for the Workload

Applying plain LRU to a workload dominated by rare, one-time large scans can flush out genuinely valuable “hot” data — a well-known problem that ARC and similar algorithms were designed to solve.

16

Best Practices & Common Mistakes

Best Practices

  • Choose an eviction policy that matches your actual access pattern — measure before guessing.
  • Always set a sensible TTL, even for “rarely changing” data, as a safety net.
  • Use jitter on TTLs to prevent synchronized mass expiration.
  • Monitor hit ratio and eviction rate continuously, not just during setup.
  • Design cache keys carefully — include versioning (like user:v2:123) so you can safely change data formats later.
  • Plan for cache failure — your system should degrade gracefully, not crash, if the cache is unavailable.

Common Mistakes

  • Forgetting to invalidate the cache after an update, leading to stale reads.
  • Setting cache size based on guesswork instead of real traffic and memory analysis.
  • Caching large objects that rarely change together with tiny, frequently changing ones in the same pool without separation.
  • Ignoring cold-start problems — a freshly restarted cache with 0% hit ratio can briefly overload the database.
i
Cache Warming

To avoid the “cold start” problem, some systems proactively load frequently used data into the cache right after a restart or deployment, before real traffic arrives. This is called cache warming.

17

Real-World & Industry Examples

Netflix

Uses a multi-layered caching architecture (EVCache, built on Memcached) across global regions to serve personalized recommendations and video metadata with very low latency, using time-based and LRU-style eviction across shards.

Amazon

Uses aggressive caching for product pages and pricing, since even a 100-millisecond delay has been shown historically to measurably affect sales — caching absorbs traffic spikes during big sales events.

Uber

Caches driver location and trip-matching data with very short TTLs (seconds) because the underlying data (driver positions) changes constantly — an example of TTL-driven eviction being essential, not optional.

Content Delivery Networks (CDNs)

Services like Cloudflare and Akamai cache static website content (images, videos, scripts) at edge servers physically close to users worldwide, evicting the least-used regional content to make room for trending content.

Twitter / X

Uses caching heavily for timelines and trending topics, since regenerating a personalized feed from scratch for every request would be far too slow at their scale.

CPUs (Intel / AMD / Apple Silicon)

Modern processors use multi-level (L1, L2, L3) hardware caches with pseudo-LRU eviction circuits, since true LRU tracking for every cache line would be too costly to build in hardware at that scale.

18

Frequently Asked Questions

Is cache eviction the same as cache invalidation?
No. Eviction happens because the cache is out of space and must remove something to make room. Invalidation happens because the cached data is known to be outdated or incorrect, regardless of space. A cache can invalidate an item even when it has plenty of free space left.
Which eviction policy should I use by default?
LRU is the most common, reliable default for general-purpose caching because it matches how most real applications behave — recently used data tends to be needed again soon. Start with LRU plus a reasonable TTL, then measure and adjust if your access patterns are unusual.
Can a cache have no eviction policy at all?
Technically yes, if the cache has unlimited or very generous space, or if TTL alone naturally keeps it small. But in almost every real production system, memory is limited, so some eviction policy is required to avoid the cache growing forever and crashing the machine.
What is a cache stampede, and how does eviction relate to it?
A cache stampede happens when a popular cached item expires or is evicted, and many simultaneous requests all rush to regenerate it from the database at once, overwhelming the system. Techniques like request coalescing and jittered TTLs help prevent this.
Do all caches use the same eviction algorithm?
No. Different layers of a system often use different policies suited to their needs — CPU caches may use pseudo-LRU in hardware, Redis supports several configurable policies (LRU, LFU, random, TTL-based), and CDNs often use custom hybrid approaches tuned for content popularity.
Is a bigger cache always better?
Not necessarily. Bigger caches cost more memory and money, and past a certain point, the hit ratio improvement becomes very small. It’s more effective to right-size the cache to the real working set and choose a smart eviction policy than to simply make it bigger.
19

Summary & Key Takeaways

Cache eviction is one of those quiet, behind-the-scenes decisions that shapes the speed, cost, and reliability of nearly every application you use every day — from the phone in your pocket to the CPU running these very words. Whenever storage is limited (and it always is), something must decide what stays and what goes. That decision is cache eviction.

Key Takeaways

  • A cache is fast, limited storage placed between users and a slower data source.
  • Eviction removes existing cached data to make room for new data when the cache is full.
  • LRU, LFU, FIFO, TTL, Clock, and ARC are the major eviction strategies, each suited to different access patterns.
  • LRU + HashMap + Doubly Linked List (O(1) operations) is the classic, interview-ready implementation pattern.
  • Real systems combine multiple techniques: tiered caching, TTL with jitter, request coalescing, and monitoring.
  • The right eviction policy is chosen based on real access patterns — not guesswork — and should always be measured with hit-ratio and eviction-rate metrics in production.
cache eviction cache invalidation LRU LFU FIFO MRU Random Replacement TTL Clock algorithm ARC Adaptive Replacement Cache Maurice Wilkes memory wall temporal locality working set hit ratio cache hit cache miss cache thrashing cache stampede thundering herd jittered TTL request coalescing single-flight HashMap Doubly Linked List LinkedHashMap ReentrantReadWriteLock ConcurrentHashMap consistent hashing sharding tiered caching CAP theorem Redis Memcached EVCache CDN Cloudflare Akamai Amazon ElastiCache Google Cloud Memorystore Azure Cache for Redis Cache-Aside Read-Through Write-Through Write-Behind ETag Cache-Control Netflix Amazon Uber CPU cache L1 L2 L3 cache Kubernetes StatefulSet cache warming