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)]

STEP 1 / 6

Empty cache

recency
∅ Empty
capacity
20

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.

EXAMPLE · FRAGMENT UNLESS A COMPLETE PROGRAM IS SHOWN

go
// 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
  1. 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.

  2. 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.

  3. 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.