Skip to the code
cf.

TTL cache

Write a class TTLCache (TTL stands for time to live). new TTLCache(defaultTtl) makes an empty cache whose entries live for defaultTtl milliseconds. The tests move time with fake timers, which control both Date.now() and setTimeout.

set(key, value, ttl) stores value under key for ttl ms, or for defaultTtl when ttl is left out. Setting a key that's already there replaces its value and starts its time again. An entry stored at time t is still there at t + ttl - 1 and gone from t + ttl on.

get(key) returns the value, or undefined when the key is missing or has expired. has(key) says whether a live entry exists, even when its value is undefined. delete(key) removes an entry and returns true if a live one was there, false otherwise. size is a getter for the number of live entries.

const cache = new TTLCache(1000);
cache.set("user", { name: "Ada" });
cache.set("token", "abc", 100);
// 100 ms later:
cache.get("token"); // undefined
cache.get("user");  // { name: "Ada" }
cache.size;         // 1

What the tests check

  1. keeps a value until its ttl has passed
    const clock = useFakeTimers();
    const cache = new TTLCache(100);
    cache.set("a", 1);
    clock.tick(99);
    expect(cache.get("a")).toBe(1);
    expect(cache.has("a")).toBe(true);
    
  2. forgets a value once its ttl has passed
    const clock = useFakeTimers();
    const cache = new TTLCache(100);
    cache.set("a", 1);
    clock.tick(100);
    expect(cache.get("a")).toBeUndefined();
    expect(cache.has("a")).toBe(false);
    
  3. a ttl passed to set overrides the default
    const clock = useFakeTimers();
    const cache = new TTLCache(100);
    cache.set("short", 1, 10);
    cache.set("long", 2, 500);
    clock.tick(10);
    expect(cache.get("short")).toBeUndefined();
    clock.tick(400);
    expect(cache.get("long")).toBe(2);
    
  4. setting a key again replaces the value and restarts its time
    const clock = useFakeTimers();
    const cache = new TTLCache(100);
    cache.set("a", 1);
    clock.tick(60);
    cache.set("a", 2);
    clock.tick(60);
    expect(cache.get("a")).toBe(2);
    clock.tick(40);
    expect(cache.get("a")).toBeUndefined();
    
  5. has() is true for a live entry whose value is undefined
    useFakeTimers();
    const cache = new TTLCache(100);
    cache.set("nothing", undefined);
    cache.set("zero", 0);
    expect(cache.has("nothing")).toBe(true);
    expect(cache.get("zero")).toBe(0);
    expect(cache.has("missing")).toBe(false);
    
  6. delete removes an entry and says whether a live one was there
    const clock = useFakeTimers();
    const cache = new TTLCache(100);
    cache.set("a", 1);
    cache.set("b", 2, 10);
    expect(cache.delete("a")).toBe(true);
    expect(cache.get("a")).toBeUndefined();
    expect(cache.delete("a")).toBe(false);
    clock.tick(10);
    expect(cache.delete("b")).toBe(false);
    
  7. size counts only live entries
    const clock = useFakeTimers();
    const cache = new TTLCache(100);
    expect(cache.size).toBe(0);
    cache.set("a", 1, 50);
    cache.set("b", 2);
    expect(cache.size).toBe(2);
    clock.tick(50);
    expect(cache.size).toBe(1);
    clock.tick(50);
    expect(cache.size).toBe(0);
    

Learn more

solution.jsSaved in this browser

Run tests runs every test on your code, here in your browser. The problem is solved when they all pass.

Ctrl + Enter runs the tests (⌘ + Enter on a Mac).