Backend / Caching / Redis / 01_what_is_redis.md

What is Redis

Updated 7 interview angles 5 min read source
On this page7
  1. Single-threaded, and why that is fine
  2. The structures, and what each is actually for
  3. When Redis is the wrong answer
  4. Against Memcached
  5. Caching patterns
  6. Related
  7. Interview angle

What is Redis

An in-memory data structure server. Not “a cache with extra features” — the data structures are the product, and choosing the right one is most of what separates a Redis answer from a memcached answer.

Persistence, memory and eviction are Persistence, eviction and memory; replication and failover are Replication, Sentinel and Cluster.

Single-threaded, and why that is fine

Command execution is single-threaded. No locks, no race between two commands, and every individual command is atomic — which is what makes INCR a counter you can trust from a hundred workers.

It is fast because it never touches disk on the read path and never pays for coordination, not because it is parallel. Redis 6+ threads I/O (and Valkey threads more of it — see Valkey), but the command loop itself stays serial.

The consequence you must remember: one slow command blocks everything.

python
r.keys("user:*")      # O(N) over the keyspace. Never.
r.scan_iter("user:*") # cursor-based, bounded work per call

KEYS, FLUSHALL, a big SMEMBERS, an unbounded LRANGE — each stalls every other client for its duration. This is the most common self-inflicted Redis outage.

The structures, and what each is actually for

Type Reach for it when
String a cached blob, a counter, a flag
Hash an object you update field by field
List a queue or a capped recent-items feed
Set membership and set algebra
Sorted set anything ranked, or a time-ordered window
Bitmap dense boolean per id, at scale
HyperLogLog approximate cardinality, fixed 12 KB
Stream an append-only log with consumer groups

The two that win interviews are the sorted set and HyperLogLog, because they replace work people otherwise do in application code:

python
# Leaderboard: ranked reads without sorting anything yourself.
r.zadd("scores", {"ada": 99, "bob": 71})
r.zrevrange("scores", 0, 9, withscores=True)   # top 10
r.zrevrank("scores", "bob")           # their position

# Unique visitors per day, 12 KB regardless of volume.
r.pfadd("visitors:2026-08-18", *user_ids)
# ~0.81% error
r.pfcount("visitors:2026-08-18")

A hash beats a string per field when you update one attribute of an object: HSET user:1 name Ada rewrites one field, where a JSON blob in a string means read, parse, mutate, serialise, write — and loses a concurrent update.

When Redis is the wrong answer

  • As the system of record. It is memory-first; persistence is a recovery aid, not a durability guarantee, and a failover can lose recent writes.
  • For a queue you cannot lose. Lists are a queue until a consumer crashes mid-item. Streams with consumer groups and acknowledgement are the honest version — Pub/sub and Streams.
  • For a working set larger than RAM. The cost curve is memory, and the answer is usually a smaller working set, not a bigger instance.
  • For querying by value. No secondary indexes in core Redis; you either maintain the index yourself or you wanted a database.

Against Memcached

Redis Memcached
Data types eight-plus strings only
Persistence RDB / AOF none
Replication yes no
Scripting Lua, functions no
Multi-threaded I/O only fully
Memory per key higher lower

Memcached is still marginally better at exactly one job — a large, purely volatile, string-only cache on a many-core box. For anything else Redis wins on capability, and the licence question (Redis 8 versus Valkey) matters more in 2026 than the Memcached comparison does.

Caching patterns

Pattern Who writes the cache
Cache-aside the application, on a miss
Write-through the cache, synchronously with the DB
Write-behind the cache, asynchronously — risks loss
Refresh-ahead a background job, before expiry

Cache-aside is the default and the one to describe, because it degrades correctly: a cache outage becomes a slow application rather than a broken one.

python
def get_user(uid: int) -> User:
    if (hit := r.get(f"user:{uid}")):
        return User.model_validate_json(hit)
    user = db.get(uid)
    r.setex(f"user:{uid}", 300, user.model_dump_json())
    return user

Two things that must be there: a TTL on every key — an entry with no expiry is a leak waiting for a deploy — and jitter on that TTL, or everything cached in the same second expires in the same second. That failure has its own note: Cache Stampede and Mitigation Patterns.

Interview angle 7

  • “Why is Redis fast?” - in-memory with no disk on the read path, and single-threaded command execution so there is no locking or coordination. Not because it is parallel — Redis 6+ threads only I/O, and the command loop stays serial.
  • “What does single-threaded cost you?” - one slow command blocks every client. KEYS on a large keyspace, FLUSHALL, or an unbounded range read is a self-inflicted outage; use SCAN and bound every range.
  • “Which data structure would you use for a leaderboard?” - a sorted set. ZADD to write, ZREVRANGE for the top N and ZREVRANK for one user’s position, all without sorting anything in application code.
  • “How would you count unique visitors at scale?” - HyperLogLog. Fixed 12 KB per key regardless of cardinality, roughly 0.81% error. A set would be exact and unboundedly large.
  • “String or hash for an object?” - a hash when you update fields independently: HSET rewrites one field, where a JSON string means read-modify-write and loses concurrent updates. A string is fine for a blob you always replace whole.
  • “Which caching pattern and why?” - cache-aside, because it degrades correctly: if Redis is down the application is slow rather than broken. Every key gets a TTL with jitter, or a synchronised expiry stampedes the database.
  • “When would you not use Redis?” - as a system of record, for a queue that cannot lose messages (use Streams with acknowledgement), when the working set exceeds RAM, or when you need to query by value.