Skip to content

LRU Cache

Evicts the least recently used item when full — Like a desk where you push old papers aside for new ones


Concept

Real-Life Analogy

** Desk workspace**: You have room for 3 books on your desk. When you need a new book, you pull it from the shelf (slow) and put it on your desk. If your desk is full, you remove the book you haven't touched in the longest time.

More examples:

  • Browser cache: Stores recently visited pages
  • Database query cache: Caches frequent query results
  • CDN: Caches frequently accessed files

LRU Cache = HashMap + Doubly Linked List

         HashMap (O(1) lookup)
         
          key → Node*      
         
                
                
  Head  [3:C] ⇄ [2:B] ⇄ [1:A]  Tail
  (MRU)                          (LRU)
OperationHowTime
get(key)Look up in HashMap, move node to headO(1)
put(key, value)If exists: update + move to head. If full: evict tail, insert at headO(1)

Code

javascript
class ListNode {
  constructor(key, value) {
    this.key = key
    this.value = value
    this.prev = null
    this.next = null
  }
}

class LRUCache {
  constructor(capacity = 5) {
    this.capacity = capacity
    this.cache = new Map()
    this.head = new ListNode(0, 0) // Dummy head
    this.tail = new ListNode(0, 0) // Dummy tail
    this.head.next = this.tail
    this.tail.prev = this.head
  }

  _remove(node) {
    node.prev.next = node.next
    node.next.prev = node.prev
  }

  _addToHead(node) {
    node.next = this.head.next
    node.prev = this.head
    this.head.next.prev = node
    this.head.next = node
  }

  get(key) {
    if (!this.cache.has(key)) return -1
    const node = this.cache.get(key)
    this._remove(node)
    this._addToHead(node)
    return node.value
  }

  put(key, value) {
    if (this.cache.has(key)) {
      const node = this.cache.get(key)
      node.value = value
      this._remove(node)
      this._addToHead(node)
      return
    }

    if (this.cache.size >= this.capacity) {
      const lru = this.tail.prev
      this._remove(lru)
      this.cache.delete(lru.key)
    }

    const newNode = new ListNode(key, value)
    this.cache.set(key, newNode)
    this._addToHead(newNode)
  }

  size() {
    return this.cache.size
  }
}

Complexity

OperationTime
getO(1)
putO(1)

Space: O(capacity) for the linked list + O(capacity) for the hash map.


Interview Questions

1. LRU Cache (LeetCode 146)

Implement an LRU Cache with O(1) get and put.

Online Playground
Output
Click ▶ Run to see results

Common Pitfalls

MistakeWhy
Not using dummy head/tailMakes edge case handling messy
Forgetting to update both prev and nextBreaks the linked list
Not deleting from Map on evictionMemory leak + stale data

Decision Guide

ScenarioRecommendation
Need O(1) get/put with bounded memoryLRU Cache
Items have different expiration timesUse LFU or TTL-based cache
Simple caching, no eviction neededA plain Map is simpler

MIT Licensed | Made with ❤️ for JS learners