Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

System Design (High-Level Design)  Module 7 – Caching

Eviction policies and cache sizing

Why a full cache must forget, how LRU, LFU, FIFO, random, TTL expiry and admission filters choose, and how to size a cache from its hit-ratio curve.

  • Intermediate
  • 20 minutes
  • Examples run with Python 3.14.8, Pyodide 314.0.7, Node.js 24.21.0 and quickjs 0.32.0
  • By MySmartCoPilot
Free intro, Pro: 4 of 20 minutes free
Free: Why a cache has to forget. With a pass: the other 8 sections and 3 interview answers, about 16 minutes.

What you will learn

  • Compare LRU, LFU, FIFO, random eviction, TTL expiry and W-TinyLFU admission
  • Estimate a cache's hit ratio from its size and the skew of its reads
  • Size a cache from the database's limit and the cost of an entry
  • Implement an LRU cache whose get and put take constant time

Before you start

What you’ll learn · Before you start

What you will learn

  • Compare LRU, LFU, FIFO, random eviction, TTL expiry and W-TinyLFU admission
  • Estimate a cache's hit ratio from its size and the skew of its reads
  • Size a cache from the database's limit and the cost of an entry
  • Implement an LRU cache whose get and put take constant time

Before you start

On this page

A 2 GB cache in front of 20 GB of product records can hold a tenth of them, so once it is full, every new entry pushes an old one out. Two decisions follow, and they belong together: which entry leaves, and how much memory the cache gets in the first place. eviction policy: The rule a full cache follows to choose which entry leaves when a new one needs room, such as LRU, LFU, FIFO or random eviction. make the first decision; cache sizing makes the second.

Why a cache has to forget

Memory is the cache’s whole budget. A catalogue of 20 million products cannot sit in a few gigabytes, so the cache keeps only part of it, and the part it keeps decides its hit ratio: The share of lookups a cache answers itself: hits divided by hits plus misses over a period. At 95 % of 20,000 lookups a second, 1,000 a second still reach the layer behind it. More in “Where caches live, from browser to database”. That ratio grows with the cache’s size, but not evenly: the first entries hold the most popular items and add many hits each, while the last ones hold items that are rarely read.

Hit ratio against cache size: a curve that rises steeply for the first entries, then flattens below the line where everything is cached.cache sizehit ratioeverything cached:only first reads missfirst entries:a big gain eachlast entries:a small gain each

Hit ratio grows with size, then flattens

Text description of the diagram

A chart with cache size on the horizontal axis and hit ratio on the vertical axis, without numbers.

  • The curve starts at zero and rises steeply: the first entries the cache can hold are the most popular items, so each one adds a lot of hits.
  • It then bends and flattens: the entries added last hold items that are rarely read, so each one adds only a few hits.
  • A dashed line near the top marks the most a cache can reach even when it holds every item, because the first read of each item is always a miss. The curve approaches the line but never reaches it.

This trace shows the flattening with real numbers. It replays 400,000 reads of 20,000 items, the popular ones read far more often, through LRU (least recently used): A cache policy that, when the cache is full, evicts the entry that has gone longest without being read. It keeps what was read recently, which is close to, but not the same as, keeping the most popular items. More in “Estimating storage, bandwidth and cache memory” caches of seven sizes:

An LRU cache's hit ratio by size Python · curve/hit_curve.py
"""Hit ratio of an LRU cache by size, on one seeded trace: 400,000 reads of 20,000 items whose popularity follows a
Zipf-like curve (the item of rank k is read in proportion to 1 / k**0.9). The same seed gives the same trace, so the
table is the same on every run."""
import random
from collections import OrderedDict
from itertools import accumulate

ITEMS, READS = 20_000, 400_000
rng = random.Random(11)
cumulative = list(accumulate(1 / k**0.9 for k in range(1, ITEMS + 1)))
trace = rng.choices(range(ITEMS), cum_weights=cumulative, k=READS)


def lru_hit_ratio(capacity):
    cache, hits = OrderedDict(), 0
    for key in trace:
        if key in cache:
            hits += 1
            cache.move_to_end(key)  # the most recently used goes to the end
        else:
            cache[key] = True
            if len(cache) > capacity:
                cache.popitem(last=False)  # forget the least recently used
    return hits / READS


print(f"{'entries':>8} {'of items':>9} {'hit ratio':>10} {'points per 1,000 more entries':>31}")
previous = None
for size in [200, 500, 1_000, 2_000, 4_000, 8_000, 16_000]:
    ratio = lru_hit_ratio(size)
    gain = "" if previous is None else f"{(ratio - previous[1]) * 100 / ((size - previous[0]) / 1_000):.1f}"
    print(f"{size:>8,} {size / ITEMS:>9.1%} {ratio:>10.1%} {gain:>31}")
    previous = (size, ratio)

Output

 entries  of items  hit ratio   points per 1,000 more entries
     200      1.0%      28.9%                                
     500      2.5%      38.9%                            33.5
   1,000      5.0%      47.7%                            17.4
   2,000     10.0%      57.3%                             9.7
   4,000     20.0%      68.2%                             5.4
   8,000     40.0%      80.3%                             3.0
  16,000     80.0%      92.6%                             1.5

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 hit_curve.py

Holding 5 % of the items answers almost half the reads. Each further 1,000 entries then adds less: 33.5 points at first, 1.5 points near the end. So cache sizing is never “as big as possible”; it is the point where the next gigabyte stops paying for itself. The curve is not a law of nature either. It comes from the skew of your own reads, which is why the same memory can answer half the reads in one system and nearly all of them in another.

Eviction policies decide which entry leaves when a new one needs room. The common ones are short to state:

  • LRU evicts the entry that has gone unused for the longest time.
  • LFU evicts the entry that has been read least often.
  • FIFO evicts the oldest entry, however often it is read.
  • Random eviction drops an entry chosen at random, which costs no bookkeeping at all.
  • TTL expiry removes an entry when its lifetime ends, whether or not memory is short.
  • An admission filter such as W-TinyLFU decides whether a new item may enter at all, from an estimate of how often it is read.

On the same memory, eviction policies can differ by ten points of hit ratio or more, and the best one depends on how the reads behave over time. Cache sizing and the policy are therefore one decision: a better policy needs less memory for the same hit ratio.

Checkpoint

Check: Why a cache must forget

  1. Question 1 of 2 In the run of hit_curve.py, how many points of hit ratio did each further 1,000 entries add between 8,000 and 16,000 entries?

    Type a number.

    A hint

    Read the last column of the last row.

    Show the answer: Question 1 of 2

    Answer: 1.5 points

    The first 300 entries beyond 200 added 33.5 points per 1,000; the last 8,000 added only 1.5 points per 1,000, as the hit ratio went from 80.3 % to 92.6 %. The first entries hold the most popular items, the last ones items that are rarely read.

  2. Question 2 of 2 Which of the mechanisms named in this section decides whether a new item may enter the cache at all, rather than which entry leaves?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: An admission filter such as W-TinyLFU

    LRU, LFU, FIFO and random eviction all admit every new item and choose a victim. An admission filter asks the earlier question, whether the newcomer should take a place at all.

How LRU, LFU, FIFO and random choose a victim Pro: opens with a Pro, Premium or Ultimate pass

In this part:

  • Admission filters: TinyLFU and W-TinyLFU
  • Five policies measured on three traces
  • Sizing a cache from a hit-ratio curve, with numbers
  • Redis eviction policies and the noeviction trap
  • Practise: build an LRU cache with constant-time get and put
    • Build an LRU cache whose get and put take constant time
  • Interview questions
    • Size a cache for a catalogue of 50 million items read 20,000 times a second, where a miss costs a 10 ms database read. How much memory do you ask for, and how do you justify it?
    • A nightly report reads every product once, and the next morning the product cache's hit ratio has fallen from 92 % to 40 %. Why, and which policy or design avoids it?
    • A Redis cache configured with noeviction fills up at peak traffic. What do users see, and how should the configuration and the code change?
  • Key takeaways

3 diagrams · 2 runnable examples (Python) · 1 exercise with sample tests · 3 interview answers · about 49 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Read a complete sample first: Design a unique ID generator

Terms in this lesson

admission filter
A cache policy that decides whether a new item may enter the cache at all, from an estimate of how often it is read. W-TinyLFU is one.
eviction policy
The rule a full cache follows to choose which entry leaves when a new one needs room, such as LRU, LFU, FIFO or random eviction.
FIFO eviction
A cache policy that evicts the oldest entry, the first one stored, however often it is read.
hit-ratio curve
A cache's hit ratio drawn against its size. It rises steeply for the first entries, which hold the most popular items, and flattens for the last ones, which hold items rarely read.
LFU (least frequently used)
A cache policy that, when the cache is full, evicts the entry that has been read least often.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.