machine coding

LRU Cache with TTL

LeetCode 146 grows up: entries also expire, and expired ones must never take space.

Medium90 minutesJava · class LruCache

the brief

What you are building

A login service keeps user sessions in memory, so it does not hit the database on every request.

Memory is limited, so the cache holds at most capacity entries. When it is full, the session used longest ago goes first — least recently used (LRU).

Sessions also expire: each one has a time-to-live (TTL). An expired session must never be returned, and must never push out a live one.

must have

Requirements

  1. put(key, value, ttlMs, now) stores the entry; it expires at now + ttlMs. An entry is alive while now < expiry.
  2. Every put first throws away all expired entries.
  3. put on a key that is still in the cache replaces its value and its expiry, and makes it the most recently used.
  4. put of a new key into a full cache (after the clean-up) evicts the least recently used live entry.
  5. get(key, now) returns the value of a live key and makes it the most recently used. A missing or expired key returns -1 (an expired one is removed).
  6. size(now) returns how many entries are alive at now.

the contract

The Java API: class LruCache

Time is a parameter, not System.currentTimeMillis(), so every test is exact. Values are int; -1 means "not found".

public LruCache(int capacity)
At most capacity live entries.
public void put(String key, int value, long ttlMs, long now)
Insert or update; may evict.
public int get(String key, long now)
The value, or -1.
public int size(long now)
The number of live entries.
  • 1 ≤ capacity ≤ 10,000
  • 1 ≤ ttlMs ≤ 10⁹
  • value can be any int, negatives included (a stored -1 simply reads back as -1)
  • now never goes backwards
  • up to 20,000 calls; aim for O(log n) or better per call

example session

Calls in, answers out

A cache of 2 sessions.

callreturnswhy
new LruCache(2)—
put("session:rahul", 1, 1000, 0)—expires at 1000
put("session:priya", 2, 5000, 0)—expires at 5000
get("session:rahul", 500)1rahul is now the most recently used
put("session:amit", 3, 5000, 600)—full: priya is the least recently used, so she goes
get("session:priya", 700)-1
get("session:rahul", 1000)-11000 < 1000 is false: expired
size(1000)1only amit
put("session:neha", 4, 100, 1000)—expires at 1100
size(1099)2
size(1100)1neha has expired

time plan

Your 90 minutes

  1. 10 minclarify — Alive at exactly the expiry time? Does put on an existing key reset the TTL? Is an expired entry ever evicted instead of a live one?
  2. 15 mindesign — Three questions: find a key (map), least recently used (linked list), what expires next (sorted by expiry). Sketch the node and its pointers.
  3. 35 mincore code — Plain LRU first: map + doubly linked list, with sentinels. Run the example without TTL. Then add expiry.
  4. 20 minedge cases — capacity 1, an update, many keys expiring at the same time, an expired key put again.
  5. 10 minpolish — Small private helpers (unlink, addFront, remove). Be ready to talk about thread safety.

Practise under real pressure: a 90-minute clock with the phases above. It keeps running if you reload the page.

your turn

Build it here

Write the whole class in the editor. Run replays the example session; Submit adds the hidden tests (edge cases and large random sessions). Stuck for 20 minutes? Open one hint at a time.

Build LruCache in the browser

Design: implement every operation. Compiled by the real javac (Java 17) and judged right here — nothing is sent anywhere.

    The reference, the data-structure reasoning and the rubric are hidden so you can build it first. Even a half-working attempt teaches you more than reading a perfect answer.

    Opening all three hints also shows it.

    revise

    Chapters that teach these structures