🔥 0
0
Lesson 3 of 6 15 min +200 XP

Eviction Policies

Caches have limited memory. When full, something must go. The eviction policy decides what gets removed.

Choose wrong, and your cache hit ratio tanks. Choose right, and you maximize performance with limited resources.

Why Eviction Matters

100GB
Data to cache
10GB
Cache capacity
???
Which 10% to keep?

The goal: Keep the data most likely to be requested again.

LRU: Least Recently Used

The most popular eviction policy. Evicts the item that hasn't been accessed for the longest time.

Intuition

If you haven't looked at something recently, you probably won't need it soon. Like cleaning your desk - papers you haven't touched in months go first.

from collections import OrderedDict

class LRUCache:
    def __init__(self, capacity):
        self.capacity = capacity
        self.cache = OrderedDict()

    def get(self, key):
        if key not in self.cache:
            return None
        # Move to end (most recently used)
        self.cache.move_to_end(key)
        return self.cache[key]

    def put(self, key, value):
        if key in self.cache:
            self.cache.move_to_end(key)
        self.cache[key] = value

        if len(self.cache) > self.capacity:
            # Remove oldest (least recently used)
            self.cache.popitem(last=False)

LRU Visualization

Cache capacity: 3 items

Access: A → Cache: [A]
Access: B → Cache: [A, B]
Access: C → Cache: [A, B, C] (full)
Access: A → Cache: [B, C, A] (A moved to end)
Access: D → Cache: [C, A, D] (B evicted - least recent)

Pros

  • Simple to understand and implement
  • Works well for most access patterns
  • O(1) operations with proper data structures

Cons

  • Doesn't consider frequency - a single access resets position
  • Scan pollution: sequential scans evict hot data

LFU: Least Frequently Used

Evicts the item with the lowest access count.

Intuition

Items accessed many times are probably important. Keep the popular ones, evict the rarely-used ones.

from collections import defaultdict

class LFUCache:
    def __init__(self, capacity):
        self.capacity = capacity
        self.cache = {}           # key -> value
        self.freq = {}            # key -> frequency
        self.freq_to_keys = defaultdict(list)  # freq -> [keys]
        self.min_freq = 0

    def get(self, key):
        if key not in self.cache:
            return None

        # Increase frequency
        old_freq = self.freq[key]
        self.freq[key] += 1
        self.freq_to_keys[old_freq].remove(key)
        self.freq_to_keys[old_freq + 1].append(key)

        # Update min_freq if needed
        if not self.freq_to_keys[self.min_freq]:
            self.min_freq += 1

        return self.cache[key]

Pros

  • Protects frequently accessed items
  • Better for skewed access patterns
  • Resistant to scan pollution

Cons

  • More complex to implement
  • "Frequency pollution" - old popular items stick around
  • New items get evicted quickly
Frequency Pollution Problem

An item accessed 1000 times last month but never since will stay in cache forever while new popular items get evicted. Solution: Use time-decayed frequency (TinyLFU).

TTL: Time To Live

Items expire after a fixed duration, regardless of access patterns.

import time

class TTLCache:
    def __init__(self):
        self.cache = {}  # key -> (value, expiry_time)

    def set(self, key, value, ttl_seconds):
        expiry = time.time() + ttl_seconds
        self.cache[key] = (value, expiry)

    def get(self, key):
        if key not in self.cache:
            return None

        value, expiry = self.cache[key]
        if time.time() > expiry:
            del self.cache[key]
            return None  # Expired

        return value

Pros

  • Guarantees data freshness
  • Simple and predictable
  • Perfect for time-sensitive data

Cons

  • Popular items may expire when still needed
  • Mass expiration can cause thundering herd
  • Choosing the right TTL is tricky

Choosing TTL Values

Data Type Typical TTL Reasoning
User session 30 min - 24 hours Security vs convenience
Product catalog 1 - 24 hours Prices/inventory change
API rate limit 1 minute - 1 hour Depends on limit window
Static assets 1 year Version in URL for updates
News articles 5 - 15 minutes Breaking news updates

FIFO: First In, First Out

Simple queue-based eviction. Oldest items go first.

Add A → [A]
Add B → [A, B]
Add C → [A, B, C] (full)
Add D → [B, C, D] (A evicted - oldest)
When to Use FIFO

Simple scenarios where recency and frequency don't matter. Rarely optimal, but easy to implement.

Random Eviction

Surprisingly effective! Just pick a random item to evict.

Pros

  • O(1) - no tracking overhead
  • No pathological worst cases
  • Works surprisingly well in practice

Cons

  • May evict hot items by chance
  • Not optimal for skewed workloads

Policy Comparison

Policy Best For Complexity
LRU General purpose, most workloads Medium
LFU Skewed access, protecting hot keys High
TTL Time-sensitive data, freshness guarantees Low
Random Simple systems, uniform access Very Low

Real-World: Redis Eviction Policies

Redis offers multiple policies you can configure:

# In redis.conf
maxmemory 100mb
maxmemory-policy allkeys-lru

Redis Eviction Policies

  • noeviction - Return errors when full
  • allkeys-lru - LRU across all keys
  • volatile-lru - LRU only among keys with TTL
  • allkeys-lfu - LFU across all keys
  • volatile-ttl - Evict keys with shortest TTL

Key Takeaways

  • LRU is the default choice - simple and effective for most cases
  • LFU protects hot keys but watch for frequency pollution
  • TTL guarantees freshness - combine with LRU/LFU for best results
  • Profile your access patterns before choosing - one size doesn't fit all

Next up: Cache Invalidation - The hardest problem in computer science.

🧠 Quick Quiz

Test your understanding of this lesson.

1

What does LRU stand for?

2

A news site has articles that get massive traffic for 24 hours, then almost none. Which eviction policy is best?

3

LFU has a 'frequency pollution' problem. What does this mean?

Caching Patterns