2026-07-14
Everyone reaches for LRU by default. But LRU tracks metadata on every read — updating a linked list or timestamp on the hot path costs cycles and contention. FIFO (First-In-First-Out) evicts based purely on insertion order and ignores reads entirely. That sounds naive, and for many workloads it is. But for some, it's the right answer.
How it works: new entries go to the tail of a queue. When the cache is full, evict from the head. Reads don't touch the queue. That's the entire algorithm — a ring buffer or a plain queue with an index map is enough.
Why it can beat LRU:
Real-world example: a CDN edge node caching immutable, hash-named assets (app.a3f9c2.js). Files never change content — they get replaced by new hashed names on the next deploy. LRU's "which was accessed most recently" signal is nearly useless because the working set rotates on deploys, not on user access patterns. FIFO gives you the same hit rate with a fraction of the CPU overhead. Facebook's MemC3 and several production HTTP caches use FIFO variants for exactly this reason.
When FIFO loses badly: when the same small set of items is accessed repeatedly amid a flood of one-hit wonders. FIFO will happily evict your hot items just because they were inserted first. This is why databases and general-purpose in-memory caches still default to LRU or LFU variants.
Rule of thumb: if your reuse distance (how many other unique accesses happen between two accesses to the same key) roughly matches your insertion order, FIFO ≈ LRU in hit rate at half the cost. If reuse distance is spiky and item popularity is skewed, stick with LRU or W-TinyLFU.
The upgrade path: if FIFO's hit rate isn't quite enough, try FIFO-Reinsertion (also called Second Chance): on eviction, check a single "referenced" bit; if set, clear it and move the entry to the tail instead of evicting. You get most of LRU's smarts with one bit of metadata and no per-read writes.
