rust 109 lines · 3 tabs

Build an O(1) LRU Cache in Rust With a HashMap and Intrusive Doubly Linked List

Shared by codesnips Jul 2026
3 tabs
use std::collections::HashMap;
use std::hash::Hash;

const NIL: usize = usize::MAX;

struct Node<K, V> {
    key: K,
    value: V,
    prev: usize,
    next: usize,
}

pub struct LruCache<K, V> {
    map: HashMap<K, usize>,
    nodes: Vec<Node<K, V>>,
    free: Vec<usize>,
    head: usize,
    tail: usize,
    cap: usize,
}
3 files · rust Explain with highlit

An LRU (least-recently-used) cache must answer two questions in constant time: given a key, where is its value, and which entry is the coldest one to evict when capacity is exceeded. A HashMap alone gives O(1) lookup but no ordering; a linked list alone gives O(1) reordering but no lookup. Combining them is the classic solution, and Lru cache shows the standard shape: a HashMap<K, usize> maps keys to node handles, while a doubly linked list keeps the entries ordered from most- to least-recently-used.

The subtle part in Rust is the list itself. A pointer-based list fights the borrow checker, so Node struct and LruCache use an arena: nodes live in a Vec<Node<K, V>> and links are plain usize indices rather than references. Freed slots are recycled through a free stack, which avoids unbounded growth as entries churn. This is an intrusive, index-based list — safe code, no unsafe, no Rc/RefCell overhead.

The internal helpers in list operations do the real work. unlink detaches a node by patching its neighbours' prev/next indices, carefully updating head or tail when the node sits at either end. push_front re-inserts a node as the new head, which is how a touched entry becomes most-recently-used. Because both operate purely on indices, they never move the underlying Vec data and stay O(1).

In public API, get looks up the index, unlinks the node, and pushes it to the front so recency is refreshed on every access. put either updates an existing entry (and promotes it) or allocates a slot — reusing a free index when available — and, once len exceeds cap, evicts the current tail via evict, removing it from the map and returning its slot to the pool.

The trade-offs are worth noting: the arena keeps allocations low and pointers safe, but stores keys twice (once in the map, once in the node for eviction cleanup). The design assumes single-threaded use; concurrent access needs an external lock. It is the right tool when a bounded, hot working set must be served with predictable constant-time cost.


Related snips

Share this code

Here's the card — post it anywhere.

Build an O(1) LRU Cache in Rust With a HashMap and Intrusive Doubly Linked List — share card
Link copied