Senior · Algorithms
LRU cache: lookup and recency form one invariant
How do you implement a bounded least-recently-used cache in Go?
Try explaining this aloud before opening the answer. State your assumptions and trace a boundary case.
ALGORITHM LAB · STEP THROUGH THE INVARIANT
capacity=2, operations=[('put', 1, 10), ('put', 2, 20), ('get', 1, 0), ('put', 3, 30), ('get', 2, 0)]
Empty cache
The front is most recent; the back is the eviction candidate.
Computed traces for these inputs. Indexes identify cell positions; active cells are labeled as well as highlighted. Compare a boundary case using the scenario selector. No code is sent to a server.
// Requires container/list. This cache has a single owner; it is not concurrent.
type lruEntry struct{ key, value int }
type LRU struct {
capacity int
order *list.List
entries map[int]*list.Element
}
func NewLRU(capacity int) *LRU {
if capacity < 0 {
capacity = 0
}
return &LRU{capacity: capacity, order: list.New(), entries: make(map[int]*list.Element)}
}
func (c *LRU) Get(key int) (int, bool) {
element, ok := c.entries[key]
if !ok {
return 0, false
}
c.order.MoveToFront(element)
return element.Value.(lruEntry).value, true
}
func (c *LRU) Put(key, value int) {
if c.capacity == 0 {
return
}
if element, ok := c.entries[key]; ok {
element.Value = lruEntry{key, value}
c.order.MoveToFront(element)
return
}
c.entries[key] = c.order.PushFront(lruEntry{key, value})
if c.order.Len() > c.capacity {
oldest := c.order.Back()
delete(c.entries, oldest.Value.(lruEntry).key)
c.order.Remove(oldest)
}
}Reveal a strong answer
A map finds a key’s list node; a doubly linked list orders recency. A hit moves its node to the front. Updating an existing key changes its value and recency without adding a duplicate node. Evict the back when capacity is exceeded.
Map and list changes must be one logical operation. If the cache is shared, guard the whole invariant with one synchronization policy; locking only the map lookup is insufficient. Returned reference values still need an ownership contract.
An entry-count limit is not a byte budget, a TTL or a coherence protocol. This integer example has no expiration or origin loader. Production caches need workload-specific byte limits, invalidation, stampede handling and metrics.
Common trap
Using a slice for recency updates makes arbitrary moves linear; evicting the front removes the most recently used item.
Complexity & use
Expected O(1) lookup/update/eviction with O(capacity) entry storage. This does not impose a process-wide memory cap or make the cache concurrent.
Try it
With capacity two: Put(1,10), Put(2,20), Get(1), Put(3,30), Get(2).
Worked solution
Recency becomes [1], [2,1], [1,2], then [3,1]. Key 2 was least recently used and is evicted; its final lookup misses. Updating key 1 must reuse its node rather than make a second copy.
Change the constraint
What changes when values have very different sizes or callers need concurrent access?
How did you do?
Self-assessment, not a compiler pass or interview score. Explain the reasoning unaided before marking understood.
Not marked. Progress stays on this browser, separate from course completion.
Primary references
Checked 2026-10-27. Use documentation for your pinned runtime, driver and tool version.