Redis Key Expiration, TTL, and Eviction Policies
Redis holds everything in memory, which raises an obvious question: what stops it from filling up and falling over? The answer is two mechanisms working together. Expiration lets keys delete themselves after a set time, which is what makes Redis safe as a cache. Eviction decides what Redis throws away when it hits its memory limit anyway. Understanding both is what separates a cache that quietly manages its memory from one that crashes with an out-of-memory error at the worst moment.
This is part 8 and the last of the fundamentals module in the Redis Masterclass, following sorted sets.
Setting expirations
You attach a time-to-live to a key, and Redis deletes it automatically when the time elapses:
SET session:abc "..." EX 3600 # expires in 3600 seconds
EXPIRE user:1:cache 300 # set a 5-minute TTL on an existing key
TTL user:1:cache # seconds remaining, -1 if none, -2 if gone
PERSIST user:1:cache # remove the TTL, make it permanent
Expiration is what makes Redis a safe cache. Cache a database query result with a 5-minute TTL, and it refreshes itself: after 5 minutes the key vanishes, the next request misses and re-populates it. You never manually clean up cached data, and stale entries can't accumulate forever. Nearly every cache key should have a TTL, because a cache without expiry is just memory you'll eventually run out of.
How expiration actually happens
A useful detail: Redis doesn't scan constantly for expired keys. It uses two mechanisms. Lazy expiration deletes an expired key when something tries to access it (the access checks the TTL and removes it if elapsed). Active expiration runs a background job that samples keys periodically and removes expired ones it finds.
The practical consequence is that an expired key still occupies memory until one of those catches it. Usually that's near-instant, but a large number of keys expiring at the same moment can briefly hold memory and cause a spike of deletion work. Spreading expirations out (adding a little randomness to TTLs) avoids a thundering-herd of simultaneous expiry, which also helps prevent the cache-stampede problem we'll cover later.
The memory limit and what happens at it
You cap Redis's memory with maxmemory. This is essential in production: without it, Redis will happily use all available RAM and then the operating system kills it. Set it to leave headroom for the OS and for Redis's own overhead:
maxmemory 4gb
maxmemory-policy allkeys-lru
The maxmemory-policy decides what Redis does when it reaches the limit and a new write needs space. This is the eviction policy, and choosing it correctly is the difference between graceful behavior and errors.
The eviction policies
When Redis hits maxmemory, the policy determines what gets removed (or whether writes fail):
- noeviction: reject writes with an error once full. Reads still work. Right when Redis holds data you can't afford to lose (a queue, a source of truth), so you'd rather fail writes than drop data.
- allkeys-lru: evict the least recently used key, across all keys. The standard choice for a pure cache: keep hot data, drop cold data.
- allkeys-lfu: evict the least frequently used key. Often better than LRU for caches, because it keeps consistently-popular keys over ones merely accessed once recently.
- volatile-lru / volatile-lfu / volatile-ttl: evict only keys that have a TTL set, by least-recently-used, least-frequently-used, or nearest expiry. Useful when you mix cache data (with TTLs, evictable) and persistent data (no TTL, protected) in one instance.
The choice follows how you use the instance. A dedicated cache wants allkeys-lru or allkeys-lfu. An instance holding a mix of cache and important data wants a volatile-* policy so only the expendable, TTL-bearing keys get evicted. An instance holding data you can't lose wants noeviction, plus monitoring so you know before it fills.
LRU vs LFU, briefly
LRU evicts what hasn't been touched recently; LFU evicts what's touched least often. LFU (added in Redis 4) is usually the better cache policy, because it protects keys that are popular over time from being evicted by a burst of one-off accesses. If you're running a cache and haven't chosen deliberately, allkeys-lfu is a good default. LRU remains fine and is the more familiar choice.
Putting it together for a cache
A well-behaved Redis cache combines all of this: every cache key gets a TTL so data refreshes and stale entries self-clean; maxmemory is set with headroom so Redis never exhausts the machine; and an allkeys-lru or allkeys-lfu policy evicts cold data gracefully when memory is tight. With those three in place, Redis manages its own memory: hot data stays, cold data ages out or gets evicted, and the instance runs indefinitely without manual cleanup or out-of-memory crashes.
Expiration and eviction are the safety mechanisms that make an in-memory store practical. TTLs keep cached data fresh and self-cleaning; maxmemory and an eviction policy keep Redis within bounds when data outgrows RAM. Set both deliberately, matched to whether the instance is a pure cache or holds data you can't lose, and Redis stays healthy under real load.
That completes the fundamentals module. Next, we start the caching module proper with the cache-aside pattern, the most common way applications use Redis.
Key takeaways
- TTLs (`EX`, `EXPIRE`) make keys self-delete, which is what makes Redis safe as a cache; nearly every cache key should have one.
- Redis expires keys lazily (on access) and actively (background sampling), so expired keys briefly hold memory until collected.
- Always set `maxmemory` in production, with headroom, or the OS will eventually kill Redis for using all RAM.
- The `maxmemory-policy` decides eviction: `allkeys-lru`/`allkeys-lfu` for caches, `volatile-*` for mixed instances, `noeviction` for data you can't lose.
- LFU usually beats LRU for caches by protecting consistently-popular keys from one-off access bursts.
Frequently asked questions
How do I make a Redis key expire?
Set a TTL with `SET key value EX seconds` or `EXPIRE key seconds` on an existing key. Redis deletes it automatically when the time elapses. Check remaining time with `TTL` and remove the expiry with `PERSIST`.
Why should cache keys have a TTL?
So cached data refreshes itself and stale entries can't accumulate forever. After the TTL, the key vanishes and the next request re-populates it. A cache without expiry is just memory that eventually fills up.
What happens when Redis reaches its memory limit?
It applies the `maxmemory-policy`: it either evicts keys (LRU, LFU, or TTL-based, over all keys or only those with a TTL) or, under `noeviction`, rejects new writes with an error. Set `maxmemory` so this is controlled rather than an OS kill.
Which eviction policy should I use?
For a pure cache, `allkeys-lru` or `allkeys-lfu`. For an instance mixing cache and persistent data, a `volatile-*` policy so only TTL-bearing keys are evicted. For data you can't lose, `noeviction` plus monitoring. LFU is often the best cache default.
What's the difference between LRU and LFU eviction?
LRU evicts the least recently accessed key; LFU evicts the least frequently accessed one. LFU usually suits caches better because it keeps consistently popular keys instead of dropping them after a burst of one-off accesses to other keys.

About the author
I'm Aman Kumar Singh, a software engineer in Noida, India building scalable full-stack products with React, Next.js, Node.js, NestJS, PostgreSQL, Redis, and AWS. I write about backend engineering, distributed systems, and system design.