Design a concurrency-safe, generic LRU cache with TTL.
Question 469HardGo 1.22 to 1.25
Classic O(1) LRU: a hash map from key to a node in a doubly linked list (container/list); move to front on access, evict from back when over capacity. TTL is checked lazily on Get. A single sync.Mutex is required (not RWMutex) because Get mutates recency.
type entry[K comparable, V any] struct {
key K
val V
expires time.Time
}
type Cache[K comparable, V any] struct {
mu sync.Mutex
cap int
ttl time.Duration
ll *list.List
items map[K]*list.Element
}
func New[K comparable, V any](capacity int, ttl time.Duration) *Cache[K, V] {
return &Cache[K, V]{cap: capacity, ttl: ttl, ll: list.New(), items: make(map[K]*list.Element)}
}
func (c *Cache[K, V]) Get(k K) (V, bool) {
c.mu.Lock()
defer c.mu.Unlock()
var zero V
el, ok := c.items[k]
if !ok {
return zero, false
}
e := el.Value.(*entry[K, V])
if time.Now().After(e.expires) {
c.ll.Remove(el)
delete(c.items, k)
return zero, false
}
c.ll.MoveToFront(el)
return e.val, true
}
func (c *Cache[K, V]) Set(k K, v V) {
c.mu.Lock()
defer c.mu.Unlock()
exp := time.Now().Add(c.ttl)
if el, ok := c.items[k]; ok {
e := el.Value.(*entry[K, V])
e.val, e.expires = v, exp
c.ll.MoveToFront(el)
return
}
c.items[k] = c.ll.PushFront(&entry[K, V]{key: k, val: v, expires: exp})
if c.ll.Len() > c.cap {
oldest := c.ll.Back()
c.ll.Remove(oldest)
delete(c.items, oldest.Value.(*entry[K, V]).key)
}
}
Follow-ups: the entry stores its key so eviction can delete from the map; shard into N caches to reduce lock contention; add a janitor goroutine for expired-but-unread entries; prevent cache stampede with singleflight (next question); returned values are shared — callers must not mutate pointer/slice values.
More on Standard Library, HTTP & Systems Design in Go
- Q467Use reflection to build a simple struct-tag validator (`validate:"required,max=10"`).
- Q468Design a per-client rate limiter for an HTTP API. Implement a token bucket.
- Q470What is a cache stampede and how does singleflight solve it?
- Q471Design an in-process pub/sub broker. How do you handle slow subscribers?
- Q472Fan out N HTTP calls with bounded concurrency, fail fast on first error, and collect results in order.
- Q473How do you unit-test HTTP handlers and HTTP clients with net/http/httptest?