๐ Building an LRU Cache in JavaScript (The Right Way)
Imagine you're building a browser, an in-memory database, or any service where you want to store recently accessed items and automatically discard the least-used ones. You need an LRU Cache โ a smart data structure that gives you:
- O(1) access to items
- Automatic eviction of the least recently used item when capacity is exceeded
In this post, weโll build an efficient LRU Cache using JavaScript, a Map, and a Doubly Linked List. Let's dive in! ๐
๐ What is an LRU Cache?
LRU stands for Least Recently Used. The idea is simple:
- Store a fixed number of key-value pairs
- When you get or put, the item becomes the most recently used
- If the cache exceeds capacity, evict the least recently used item
This keeps your cache optimized, fast, and memory-efficient.
๐ง What Weโll Use
Weโll combine two data structures:
- Map โ stores key โ node (for O(1) lookups)
- Doubly Linked List โ tracks the order of usage from least recent (left) to most recent (right)
We also use two dummy nodes (left and right) to make insertions/removals cleaner without edge-case checks.
๐ง Our Custom Helper Methods
To keep our logic clean, we write two internal utility methods.
  
  
  1. _remove(node) โ Remove Node from List
LRUCache.prototype._remove = function (node) {
    node.prev.next = node.next;
    node.next.prev = node.prev;
};
โ
 Purpose: Detach a node from the doubly linked list.
๐ When it's used:  
- When an existing key is accessed (to reinsert it as most recent)
- When evicting the least recently used node
  
  
  2. _insert(node) โ Insert Node at MRU End
LRUCache.prototype._insert = function (node) {
    const prev = this.right.prev;
    const next = this.right;
    prev.next = node;
    node.prev = prev;
    node.next = next;
    next.prev = node;
};
โ
 Purpose: Always insert a node just before the right dummy (i.e., mark it as most recently used)
๐ When it's used:  
- After inserting a new node
- After accessing an existing node (to update its position)
  
  
  ๐คซ Why the _ prefix in _remove and _insert?
Itโs just a naming convention. It signals that these methods are internal helpers and shouldnโt be used outside the class. JavaScript doesn't enforce private methods in traditional ES5 syntax, so _ helps you communicate intent.
๐งฑ Full LRU Cache Implementation
Hereโs the complete working code ๐

// Node class for the doubly linked list
class Node {
    constructor(key, value) {
        this.key = key;
        this.value = value;
        this.prev = null;
        this.next = null;
    }
}
var LRUCache = function (capacity) {
    this.capacity = capacity;
    this.cache = new Map(); // key โ node
    // Dummy nodes: left = LRU end, right = MRU end
    this.left = new Node(0, 0);
    this.right = new Node(0, 0);
    this.left.next = this.right;
    this.right.prev = this.left;
};
// Removes a node from the doubly linked list
LRUCache.prototype._remove = function (node) {
    node.prev.next = node.next;
    node.next.prev = node.prev;
};
// Inserts a node at the MRU (right) end
LRUCache.prototype._insert = function (node) {
    const prev = this.right.prev;
    const next = this.right;
    prev.next = node;
    node.prev = prev;
    node.next = next;
    next.prev = node;
};
// Get a value by key and update it to be most recent
LRUCache.prototype.get = function (key) {
    if (this.cache.has(key)) {
        const node = this.cache.get(key);
        this._remove(node);  // remove from current position
        this._insert(node);  // re-insert as most recent
        return node.value;
    }
    return -1;
};
// Insert or update a key-value pair
LRUCache.prototype.put = function (key, value) {
    if (this.cache.has(key)) {
        this._remove(this.cache.get(key));
    }
    const node = new Node(key, value);
    this.cache.set(key, node);
    this._insert(node);
    // Evict LRU if capacity exceeded
    if (this.cache.size > this.capacity) {
        const lru = this.left.next;  // least recently used node
        this._remove(lru);
        this.cache.delete(lru.key);
    }
};
๐ง Time and Space Complexity
| Operation | Time Complexity | Why | 
|---|---|---|
| get | O(1) | Map lookup + pointer updates | 
| put | O(1) | Map insert + pointer operations | 
| Space | O(N) | Stores up to capacitynodes | 
โ Summary
By combining a Map and a Doubly Linked List, we achieve:
- O(1) performance for both getandput
- Clean and predictable memory eviction
- Modular, readable code with private helper functions
This structure is battle-tested and used across real-world systems โ from operating system memory managers to Redis and beyond.
 




 
    
Top comments (0)