With buckets and minFreq in place, eviction is a single lookup, since the victim is at the front of the minimum-frequency bucket. Today you replace the O(n) scan with that O(1) move and watch a well-used key survive while a cold one is dropped.
Evict the front of the minFreq bucket in O(1), replacing the linear scan.
Everything the last three lessons built was to make this one line cheap. The
eviction victim is the least-frequently-used key, and on a tie the least-recently-used
among them - which is precisely the front of the minFreq bucket, because that
bucket holds exactly the lowest-frequency keys in use order. So evict grabs that
front element, unlinks it, and deletes it from the map: one lookup, no scan, O(1).
The scenario shows LFU’s whole personality. Keys 1 and 2 were used repeatedly, key 3 only on insert, so when the fourth key needs room it is key 3 - the cold one - that goes, even though key 1 was inserted first. An LRU cache would have made a different call, and that difference is the story of the capstone. With insert, use, and eviction all constant time, you now have two complete caches: an LRU driven by recency and an LFU driven by frequency. Next you give them shared conveniences - TTL, resize, and stats - then run them head to head.
// the victim is the least-recently-used key at the lowest frequency:// the Front of the minFreq bucket. O(1), no scan.func (c *LFU) evict() {l := c.buckets[c.minFreq]front := l.Front()victim := front.Value.(int)l.Remove(front)delete(c.data, victim)}// Put still calls evict() before inserting a new key into a full cache.