How to implement a Least Frequently Used LFU cache?
System Design practice on Codemia
Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.
Introduction
An LFU cache evicts the key that has been used the fewest times. To make that practical, the cache must update frequency counts quickly and still support fast get and put operations.
What an LFU Cache Needs to Track
A usable LFU cache needs more than a dictionary of key-value pairs. It must track:
- the value for each key
- the access frequency for each key
- which keys share the same frequency
- the current minimum frequency in the cache
If you only store a counter per key and scan the whole cache on eviction, the algorithm becomes too slow.
The standard approach is:
- a map from key to node data
- a map from frequency to an ordered bucket of keys
- a
min_freqvalue so eviction knows where to look first
When multiple keys have the same frequency, many implementations evict the least recently used key among that frequency bucket.
Python Implementation
The following implementation uses OrderedDict to keep insertion order inside each frequency bucket.
This design gives amortized O(1) operations for the typical interview-style LFU cache API.
How the Eviction Rule Works
Suppose the cache contains:
- key 1 with frequency 3
- key 2 with frequency 1
- key 3 with frequency 1
If the cache is full and a new key arrives, the eviction must come from the minimum-frequency bucket, which is frequency 1. If multiple keys share that bucket, the oldest one in that bucket is removed first.
That tie-breaker is important because otherwise LFU alone is ambiguous.
Why min_freq Matters
Without min_freq, eviction would require scanning all frequencies to find the smallest one still in use. That would destroy performance.
By updating min_freq every time a key changes buckets, the cache always knows where the current eviction candidate lives.
This is the subtle part of the implementation:
- insert new key, set
min_freq = 1 - move key from one bucket to the next on every access
- if a frequency bucket becomes empty and it was the minimum, increment
min_freq
If that bookkeeping is wrong, the cache will evict the wrong item even if the rest of the data structure looks fine.
When LFU Is a Good Choice
LFU works best when long-term popularity matters more than short-term recency. For example, if a small set of keys is accessed repeatedly over time, LFU tends to protect them better than LRU.
On the other hand, LFU can be a poor fit when access patterns shift quickly. A key that was hot in the past can keep an artificially high frequency and resist eviction longer than it should.
That tradeoff is why real systems sometimes use approximate LFU or hybrid policies instead of a strict textbook implementation.
Common Pitfalls
Updating the frequency counter on put for an existing key but forgetting to move the key to the next frequency bucket makes the internal state inconsistent.
Evicting from the global oldest key instead of the oldest key in the minimum-frequency bucket changes the policy from LFU-with-LRU-tiebreak to something else.
Forgetting to reset min_freq to 1 when inserting a fresh key after eviction causes later evictions to look in the wrong bucket.
Ignoring the capacity-zero case leads to awkward bugs where the cache appears to accept writes but cannot actually store anything.
Summary
- An LFU cache tracks values, frequencies, and the current minimum frequency.
- Efficient implementations use a key map plus per-frequency ordered buckets.
- '
min_freqis what keeps eviction fast.' - When frequencies tie, evicting the least recently used key within that bucket is a practical rule.
- The hardest part is not storing counts, but keeping bucket transitions and
min_freqcorrect.
Related reading
- How to implement a microservice Event Driven architecture with Spring Cloud Stream Kafka and Database per service
- How to implement cancellation in Request Reply Pattern in .NET?
- How to implement contract testing when kafka is involved in microservice architecture?
- how to implement eigenvalue calculation with MapReduce/Hadoop?
- How to implement a Median-heap
- How to implement a queue with three stacks?
- How to implement LFU cache using STL?
- How to introduce delay in rebalancing in case of kafka consumer group?

System Design Fundamentals
Build a strong foundation in designing scalable, reliable distributed systems.
View the courseTrack what you have practised
A free account saves your progress, solutions and study plan across every problem on Codemia.
System Design practice on Codemia
Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.