Redis Is More Than a Cache
Most teams use Redis as a key-value cache and stop there. But its data structures make it a rate limiter, a queue, a leaderboard, and a pub/sub bus — here's the Redis I actually reach for on backends.
Ask most developers what Redis is and you'll hear "a cache." It is a fantastic cache. But reducing Redis to GET/SET is like calling a Swiss Army knife a bottle opener. Its real power is a set of in-memory data structures with atomic operations, and once you see them, half the auxiliary services you were about to build turn into a few Redis commands.
I've leaned on it across multiple backends. Here's the Redis beyond the cache.
Why in-memory changes what's possible
Redis keeps data in RAM, which makes operations sub-millisecond. That speed is what lets it serve roles a disk-based database would be too slow for — a rate-limit check on every request, a leaderboard updated on every score. The trade is that RAM is finite and volatile, so Redis is for data that's hot, ephemeral, or reconstructable — not your source of truth.
Caching, done right
Start with the obvious use, but do the part people skip — set an expiry:
SET user:42 "{...}" EX 3600 # cache for 1 hour, then auto-expireThe EX is not optional. A cache without expiry is a memory leak with extra steps, and stale-forever data is worse than no cache. Every cached key should have a TTL that reflects how long the data can safely be wrong.
Rate limiting
This is my most-used non-cache pattern. Redis's atomic INCR plus an expiry gives you a per-user request counter in two commands:
INCR rate:user:42 # atomic; returns the new count
EXPIRE rate:user:42 60 # first hit starts a 60s window
# if the returned count > limit, reject with 429Because INCR is atomic, concurrent requests can't race past the limit — the count is always exact. No table, no locks, no separate service.
A job queue
A Redis list is a queue: push on one end, pop (blocking) off the other. A producer enqueues work; a worker waits for it.
LPUSH jobs "{...}" # producer adds a job
BRPOP jobs 0 # worker blocks until a job arrivesThis is the backbone of the background-processing I've built — get slow work off the request path and let a pool of workers drain the queue. Libraries like Bull wrap exactly this with retries and scheduling, but the primitive underneath is a Redis list.
Leaderboards and rankings
A sorted set keeps members ordered by a score, and stays sorted as you update it. That's a leaderboard for free:
ZADD leaderboard 1500 "user:42" # add/update a score
ZREVRANGE leaderboard 0 9 WITHSCORES # top 10, already ranked
ZREVRANK leaderboard "user:42" # this user's exact rankComputing ranking from a relational table means an ORDER BY over the whole set on every read. A sorted set maintains the order incrementally, so "top 10" and "my rank" are instant.
Pub/sub and ephemeral state
Two more that come up constantly: pub/sub for broadcasting events to subscribers (I've used it behind Socket.io to fan real-time updates across server instances), and simple shared ephemeral state — sessions, feature-flag caches, "who's online" sets — that multiple app instances need to read fast and don't need to persist forever.
The mental model
Stop thinking "Redis = cache" and start thinking "Redis = fast, atomic data structures I can share across processes." The question becomes: is this data hot, short-lived, or reconstructable, and would an in-memory structure make it trivial? Rate limits, queues, leaderboards, sessions, real-time fan-out — all yes. Keep your source of truth in a durable database, and let Redis absorb the fast, ephemeral, high-frequency work it's uniquely good at. That division of labor is one of the most reliable performance patterns I know.