TTL & Expiry

beginner redis ttl expiry caching

A TTL (time-to-live) is a countdown attached to a Redis key. When the TTL hits zero, the key is gone. This is how Redis behaves like a cache instead of a forever-store.

In simple language — think of it like a self-destructing message. You set the timer, and after that time elapses, Redis throws the key away. You don’t have to remember to delete it.

The basic commands

SET session:abc123 "user_42"
EXPIRE session:abc123 3600        # expires in 3600 seconds (1 hour)

# or do it in one shot
SET session:abc123 "user_42" EX 3600

TTL session:abc123                 # → 3599 (seconds remaining)
PTTL session:abc123                # → 3599042 (milliseconds remaining)

PERSIST session:abc123             # removes the TTL — key now lives forever

TTL return values worth knowing:

 -2  → key does not exist
 -1  → key exists but has no TTL
  N  → key exists, expires in N seconds

You can also set an absolute expiry time (Unix timestamp):

EXPIREAT session:abc123 1735689600
SET token "xyz" EXAT 1735689600

How does Redis actually expire keys?

Here’s the cool part. Redis doesn’t run a timer per key — that would be insanely expensive with millions of keys. Instead it uses a hybrid strategy: lazy expiration plus active expiration.

Two ways a key gets evicted
Lazy
When a client touches the key, Redis checks the TTL. Expired? Delete and return nil.

Cost: O(1) per access
Active
10× per second, Redis samples 20 random keys with TTLs. Expires the dead ones. If >25% were dead, immediately repeat.

Probabilistic sweep

The active algorithm in pseudocode:

every 100ms:
  loop:
    sample 20 keys from the "keys with TTL" set
    delete the expired ones
    if more than 25% were expired:
      continue (don't sleep, do another round)
    else:
      break

This means a key with a TTL might stick around in memory for a brief moment after its expiry — but no client will ever see it as alive, because the lazy check kicks in on access.

Common use cases

  • SessionsSET session:<id> <data> EX 1800 for 30-minute sessions
  • Rate limiting — counters that auto-reset
  • OTP / verification codesSET otp:<phone> 123456 EX 300 (5 min window)
  • Cache entries — keep hot data fresh, let stale data fall away
# rate limit: max 100 reqs/min per user
INCR ratelimit:user:42
EXPIRE ratelimit:user:42 60 NX    # NX = only set TTL if there isn't one

NX on EXPIRE (Redis 7+) is handy — only sets the TTL if no TTL exists. So the first INCR sets the window, subsequent INCRs don’t reset it.

Gotchas

  • Renaming a key with RENAME keeps the TTL. Copying with COPY does too (unless you pass options).
  • SET key value without EX clobbers any existing TTL. Use SET key value KEEPTTL to preserve it.
  • If you only check TTL, you can’t tell “no key” from “no TTL” without combining with EXISTS. That’s why -2 vs -1 matters.

TTLs are how Redis stays small and stays cache-y. When memory pressure exceeds what TTLs can handle, eviction policies take over.