Design a data structure that follows the constraints of a Least Recently Used (LRU) cache.
Implement the LRUCache
class:
LRUCache(int capacity)
Initialize the LRU cache with positive size capacity
.int get(int key)
Return the value of the key
if the key exists, otherwise return -1
.void put(int key, int value)
Update the value of the key
if the key
exists. Otherwise, add the key-value
pair to the cache. If the number of keys exceeds the capacity
from this operation, evict the least recently used key.The functions get
and put
must each run in O(1)
average time complexity.
Example 1:
Input ["LRUCache", "put", "put", "get", "put", "get", "put", "get", "get", "get"] [[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]] Output [null, null, null, 1, null, -1, null, -1, 3, 4] Explanation LRUCache lRUCache = new LRUCache(2); lRUCache.put(1, 1); // cache is {1=1} lRUCache.put(2, 2); // cache is {1=1, 2=2} lRUCache.get(1); // return 1 lRUCache.put(3, 3); // LRU key was 2, evicts key 2, cache is {1=1, 3=3} lRUCache.get(2); // returns -1 (not found) lRUCache.put(4, 4); // LRU key was 1, evicts key 1, cache is {4=4, 3=3} lRUCache.get(1); // return -1 (not found) lRUCache.get(3); // return 3 lRUCache.get(4); // return 4
Constraints:
1 <= capacity <= 3000
0 <= key <= 104
0 <= value <= 105
2 * 105
calls will be made to get
and put
.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:
In this post, weโll build an efficient LRU Cache using JavaScript, a Map, and a Doubly Linked List. Let's dive in! ๐
LRU stands for Least Recently Used. The idea is simple:
This keeps your cache optimized, fast, and memory-efficient.
Weโll combine two data structures:
We also use two dummy nodes (left
and right
) to make insertions/removals cleaner without edge-case checks.
To keep our logic clean, we write two internal utility methods.
_remove(node)
โ Remove Node from List1LRUCache.prototype._remove = function (node) { 2 node.prev.next = node.next; 3 node.next.prev = node.prev; 4};
โ
Purpose: Detach a node from the doubly linked list.
๐ When it's used:
_insert(node)
โ Insert Node at MRU End1LRUCache.prototype._insert = function (node) { 2 const prev = this.right.prev; 3 const next = this.right; 4 prev.next = node; 5 node.prev = prev; 6 node.next = next; 7 next.prev = node; 8};
โ
Purpose: Always insert a node just before the right
dummy (i.e., mark it as most recently used)
๐ When it's used:
_
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.
Hereโs the complete working code ๐
1// Node class for the doubly linked list 2class Node { 3 constructor(key, value) { 4 this.key = key; 5 this.value = value; 6 this.prev = null; 7 this.next = null; 8 } 9} 10 11 12 13var LRUCache = function (capacity) { 14 this.capacity = capacity; 15 this.cache = new Map(); // key โ node 16 17 // Dummy nodes: left = LRU end, right = MRU end 18 this.left = new Node(0, 0); 19 this.right = new Node(0, 0); 20 this.left.next = this.right; 21 this.right.prev = this.left; 22}; 23 24// Removes a node from the doubly linked list 25LRUCache.prototype._remove = function (node) { 26 node.prev.next = node.next; 27 node.next.prev = node.prev; 28}; 29 30// Inserts a node at the MRU (right) end 31LRUCache.prototype._insert = function (node) { 32 const prev = this.right.prev; 33 const next = this.right; 34 prev.next = node; 35 node.prev = prev; 36 node.next = next; 37 next.prev = node; 38}; 39 40// Get a value by key and update it to be most recent 41LRUCache.prototype.get = function (key) { 42 if (this.cache.has(key)) { 43 const node = this.cache.get(key); 44 this._remove(node); // remove from current position 45 this._insert(node); // re-insert as most recent 46 return node.value; 47 } 48 return -1; 49}; 50 51// Insert or update a key-value pair 52LRUCache.prototype.put = function (key, value) { 53 if (this.cache.has(key)) { 54 this._remove(this.cache.get(key)); 55 } 56 57 const node = new Node(key, value); 58 this.cache.set(key, node); 59 this._insert(node); 60 61 // Evict LRU if capacity exceeded 62 if (this.cache.size > this.capacity) { 63 const lru = this.left.next; // least recently used node 64 this._remove(lru); 65 this.cache.delete(lru.key); 66 } 67};
Operation | Time Complexity | Why |
---|---|---|
get | O(1) | Map lookup + pointer updates |
put | O(1) | Map insert + pointer operations |
Space | O(N) | Stores up to capacity nodes |
By combining a Map and a Doubly Linked List, we achieve:
get
and put
This structure is battle-tested and used across real-world systems โ from operating system memory managers to Redis and beyond.
Published by: Rakesh Reddy Peddamallu
Top Comments (0)