Beginner7 min readlive prototype

TTL — Time To Live

Eviction by clock, not by capacity. The right tool for data with an intrinsic lifetime.

Overview

What this concept solves

TTL — Time To Live — is the only policy on this list that doesn't care about access patterns or cache pressure at all. Every entry comes with an expiry timestamp. When the clock passes that timestamp, the entry is invalid: gone from the cache, regardless of capacity or recency.

TTL isn't really competing with LRU and friends — it's solving a different problem. The others answer 'I'm out of room, what should I drop?' TTL answers 'this data has a natural lifetime; don't serve it past that point.' DNS records, session tokens, OTP codes, signed URLs, and rate-limit counters all have intrinsic expiry — TTL is the right tool for all of them, and it's almost always combined with one of the others.

Mechanics

How it works

Two patterns: passive and active expiry

State per entry is (value, expiresAt). There are two main strategies for actually evicting expired entries:

  • Passive (lazy) — check expiresAt on every read. If expired, treat as a miss and delete. This is cheap on writes but means expired entries can squat in memory indefinitely if no one reads them.
  • Active (sweep) — a background scanner walks the cache periodically and deletes anything past its expiry. Bounds memory usage but adds CPU and lock churn.

Real systems use both at once. Redis, for example, does passive expiry on every access and runs a probabilistic active sampler 10 times a second to clean up entries that no one reads.

TTL composes with other policies

Almost every cache supports TTL alongside its main eviction policy. Redis has allkeys-lru and volatile-lru (only consider keys with a TTL). Caffeine has expireAfterWrite and expireAfterAccess. TTL is rarely the policy; it's a co-pilot.

expireAfterWrite vs expireAfterAccess

  • expireAfterWrite — the timer starts at insertion and never resets. Use for data with a hard freshness contract (DNS records, signed JWTs).
  • expireAfterAccess — every read resets the timer. Use for session-like data where 'still being used' should keep it alive.

Interactive prototype

Run it. Break it. Tune it.

Sandboxed simulation embedded right in the page. No setup, no install.

About this simulation

Each item carries its own countdown. Set the TTL slider (3–15 s) before inserting, then click a letter to add it. The bar under each item drains in real time, then the item vanishes. Re-inserting a letter refreshes its timer — TTL is a freshness window, not a capacity cap.

Hands-on

Try these on your own

Open the prototype above, run each experiment, predict the answer, then verify.

try 01

Watch eviction by the clock

Drag the slider to 3s. Click A. The countdown bar drains in real time and A disappears at 0 — no miss, no pressure, just time. That's eviction by clock.

try 02

Refresh the timer

Set the slider to 10s. Click A. While the bar is mid-drain, click A again. The timer resets to a fresh 10s — re-inserting the same key with a TTL is how you tell the cache 'keep this alive'.

try 03

Mix different lifetimes

Set 4s and click A. Bump the slider up to 12s and click B. Both share the same cache, but A vanishes long before B. TTL is per-item, not global — every key can have its own freshness contract.

In practice

When to use it — and what you give up

When to reach for it

  • DNS records — the canonical TTL use case. Every record carries its own TTL in seconds.
  • Session tokens, OAuth refresh tokens, OTPs — must be invalid past a known point.
  • Signed URLs and pre-signed credentials — TTL must match the signature window.
  • Rate-limit counters — the bucket itself needs to expire when the window rolls over.
  • As a freshness layer on top of LRU/LFU/etc. — "keep the popular keys, but never serve anything older than 30 minutes."

Real-world example

Redis lets you set a TTL on any key with EXPIRE. Internally, it stores expiries in a separate hash and uses lazy expiry on read plus an active sampler. Caffeine in Java offers expireAfterWrite, expireAfterAccess, and a custom Expiry for per-entry timers.

Pros

  • Matches the problem when data has a real lifetime — no other policy gets the semantics right.
  • Predictable upper bound on staleness — easy to reason about and easy to communicate.
  • Cheap when paired with passive (lazy) expiry — no background work needed.
  • Composes with any other eviction policy.

Cons

  • Doesn't bound memory by itself — without capacity-based eviction, an idle-but-not-expired cache can balloon.
  • Active sweepers cost CPU and can contend with reads. Probabilistic sampling helps but isn't free.
  • Hard freshness on the wrong data hurts hit rate: short TTLs flush hot entries that the workload would happily reuse.

Reference

Code & further reading

A minimal reference implementation and pointers worth bookmarking.

ttl.ts
// TTL with lazy + active expiry. Compose with another policy
// for capacity-based eviction.
class TTLCache<K, V> {
  private store = new Map<K, { value: V; expiresAt: number }>();

  constructor(private defaultTtlMs: number) {
    // Active sweep: every 1s, drop a sample of expired keys.
    setInterval(() => this.activeSweep(), 1000);
  }

  set(key: K, value: V, ttlMs = this.defaultTtlMs): void {
    this.store.set(key, { value, expiresAt: Date.now() + ttlMs });
  }

  get(key: K): V | undefined {
    const entry = this.store.get(key);
    if (!entry) return undefined;
    if (Date.now() >= entry.expiresAt) {
      this.store.delete(key); // lazy expiry on read
      return undefined;
    }
    return entry.value;
  }

  private activeSweep(): void {
    const now = Date.now();
    // Sample a few random keys and evict those that expired.
    const keys = Array.from(this.store.keys());
    for (let i = 0; i < Math.min(20, keys.length); i++) {
      const k = keys[Math.floor(Math.random() * keys.length)];
      const entry = this.store.get(k);
      if (entry && now >= entry.expiresAt) this.store.delete(k);
    }
  }
}

References & further reading

3 sources

Knowledge check

Did the prototype land?

Quick questions, answers revealed on submit. No scoring saved.

question 01 / 03

What's the difference between 'lazy' and 'active' TTL expiry?

question 02 / 03

Which scenario is the textbook fit for TTL eviction?

question 03 / 03

Why is TTL almost always combined with another eviction policy in production?

0/3 answered

Was this concept helpful?

Tell us what worked, or what to improve. We read every note.