TTL stands for time to live: how long a stored value stays valid, usually given in milliseconds or seconds. A TTL cache gives each entry a TTL when it's stored. Until that much time has passed, reading the entry returns it. From then on the entry has expired, and reading it gives back nothing, as if it had never been stored.
It's for data that goes out of date. An API response, a user's profile, or an auth token can be reused for a while instead of being fetched again every time it's needed, but not forever. The TTL says how old a value may get before you'd rather fetch a fresh one: a minute for a list of products, say.
Here's an entry stored with a TTL of 60 seconds, then read at a few moments:
| Time | What happens | Result |
|---|---|---|
| 0 s | Store user with a TTL of 60 s. | Stored |
| 10 s | Read user. | Found: it's 10 s old. |
| 59.999 s | Read user. | Found: it's 1 ms short of 60 s old. |
| 60 s | Read user. | Not found: it's 60 s old, so it has expired. |
| 75 s | Store user again. | Stored, with a fresh 60 s |
| 100 s | Read user. | Found: the new value is 25 s old. |
A TTL cache and an LRU (least recently used) cache throw entries out for different reasons. An LRU cache goes by use: when it's full, the entry that has gone untouched the longest is evicted, however new it is. A TTL cache goes by age: an entry expires when its time is up, however often it's read, and reading it doesn't make it live any longer. Some caches do both.
TTLs are all over the web. Cache-Control: max-age=3600 on a response tells the browser it's fresh for an hour, so it can be reused without asking the server. In DNS, the system that turns domain names into IP addresses, every record comes with a TTL, in seconds, that says how long it may be cached. A cookie's Max-Age attribute is the number of seconds until it expires. And apps keep API responses for a set time to save requests.
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
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);
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);
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);
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();
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);
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);
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);
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).
I'm here to help with TTL cache. I give hints, never the solution, so you get to solve it yourself. Ask for a hint, select code to ask about it, or press Ask Tuto why on a failing test.
Your first 3 answers need no account. for free to keep asking.