Strings, hashes, lists, sets and sorted sets
Most caches store one thing: an opaque blob under a key. Redis stores typed data structures — strings, hashes, lists, sets, sorted sets — each with atomic operations that run server-side. This is what makes Redis more than a cache, and this lesson is the five you will use, with the commands run against a real Redis 7 so the output is exactly what you will see.
Everything is still a key pointing at a value. What changes is what the value is, and what you can do to it in one atomic step.
Strings — the workhorse
The value is a string (or a number, which is a string Redis can do arithmetic on):
SET book:1:title "The Long Monsoon" → OK
GET book:1:title → "The Long Monsoon"
INCR book:1:views → 1
INCR book:1:views → 2
INCR is the one to notice: it increments atomically on the server, with no read-modify-write
race. Two clients incrementing a view counter at once cannot lose a count — the same lost-update
safety as PostgreSQL's UPDATE ... SET n = n + 1, but at Redis speed. INCRBY, DECR and
INCRBYFLOAT round it out.
The naming convention — book:1:title, member:42:name — is not enforced; Redis has no
schema. But colon-separated keys are the universal Redis idiom, and they keep an otherwise flat
keyspace navigable. Adopt it.
Strings are where you cache serialised objects too: SET book:1 '{"title":"...","copies":3}' with
a JSON blob is the simplest cache-a-row pattern, and often all you need.
Hashes — a record under one key
When you want fields, not a blob:
HSET member:1 name "Kavita Joshi" membership senior → 2
HGETALL member:1
1) "name"
2) "Kavita Joshi"
3) "membership"
4) "senior"
A hash is a map of field→value under a single key — a row, essentially. HGET member:1 name reads
one field without deserialising the whole thing, and HINCRBY member:1 loans 1 increments one
numeric field atomically. Prefer a hash over a JSON string when you update individual fields, and a
JSON string when you always read and write the whole object.
Lists — ordered, and a queue
A list is an ordered sequence, fast to push and pop at either end:
RPUSH queue:emails "a@x.com" "b@x.com" → 2 (append right)
LPUSH queue:emails "urgent@x.com" → 3 (prepend left)
LRANGE queue:emails 0 -1
1) "urgent@x.com"
2) "a@x.com"
3) "b@x.com"
LPOP queue:emails → "urgent@x.com"
Push on one end, pop from the other and you have a queue — the basis of a simple job queue.
BLPOP blocks until an item arrives, so a worker can wait efficiently rather than polling. -1 as
an index means "the last element", the same convention as Python. Keep lists bounded (LTRIM) or
they grow forever — the unbounded-collection trap, again.
Sets — membership and uniqueness
An unordered collection of unique members:
SADD book:1:tags fiction literary fiction → 2 (the duplicate is ignored)
SMEMBERS book:1:tags → "fiction", "literary"
SCARD book:1:tags → 2 (count)
SISMEMBER book:1:tags fiction → 1 (membership test, O(1))
Adding fiction twice added it once — uniqueness is free. SISMEMBER is an instant membership
test, which is the point: "has this user seen this article", "is this IP blocked". And sets do
set algebra on the server — SINTER (intersection), SUNION, SDIFF — so "users who follow
both A and B" is one command. SADD on a "unique visitors today" set is a cheap approximate-free
way to count distinct things.
Sorted sets — the one that surprises people
A set where every member carries a score, and members are kept ordered by that score. This is the structure that makes Redis famous, because a leaderboard is a one-liner:
ZADD leaderboard 100 kavita 250 ravi 175 neha → 3
ZREVRANGE leaderboard 0 -1 WITHSCORES
1) "ravi" 250
2) "neha" 175
3) "kavita" 100
ZRANK leaderboard ravi → returns rank ascending
ZINCRBY leaderboard 50 kavita → 150 (bump a score atomically)
Ranked, top-N, "give me positions 10–20", "what rank is this user" — all fast, all maintained
automatically as scores change. The uses go well beyond games: a priority queue (score =
priority), a time-ordered feed (score = timestamp), rate limiting (score = request time),
and range queries (ZRANGEBYSCORE for "everyone scoring 100–200"). When you need "sorted by a
number, updated live", this is the structure.
Keys, expiry and inspection
Operations on keys themselves, which apply whatever the value type:
EXISTS book:1 → 1
DEL book:1 → 1
TYPE member:1 → hash
EXPIRE session:abc 3600 → 1 (delete after 3600 seconds)
TTL session:abc → 3600 (seconds left; -1 = no expiry, -2 = gone)
SET session:abc "u42" EX 100 (set with expiry in one atomic step)
EXPIRE and TTLs are central to using Redis as a cache — they are the whole next-but-one lesson —
and setting the value and its expiry together (SET ... EX) avoids a window where the key exists
forever because the process died between the two commands.
One warning: never run KEYS * on a production server. It scans the entire keyspace and blocks
Redis — which is single-threaded — for as long as it takes, freezing every other client. Use
SCAN, which returns keys in small batches without blocking. This is the single most common way
people accidentally take Redis down.
Beyond the five
Redis has more when you need it: streams (an append-only log, for event pipelines — module 12 touches this), bitmaps and HyperLogLog (count distinct things in tiny fixed memory — "unique visitors" in 12 KB regardless of count), geospatial indexes, and pub/sub for messaging. You will not reach for these often, but knowing they exist stops you rebuilding them.
Atomicity and doing several things at once
Every single command above is atomic — Redis runs one command at a time. For several commands as a
unit, MULTI/EXEC groups them into a transaction (though without the rollback semantics of SQL),
and a Lua script (EVAL) runs a whole block atomically on the server, which is how the correct
rate limiter in a later lesson is built. The single-threaded model that makes Redis fast is also
what makes this atomicity simple.
Check your work
What makes Redis more than a plain cache. Typed data structures with atomic server-side operations.
What is still constant across all of them. A key pointing at a value.
Why INCR matters. It increments atomically, immune to the lost-update race.
The key-naming idiom. Colon-separated (book:1:title) — unenforced but universal.
Hash versus JSON string. Hash when you update individual fields; JSON string when you read and write the whole object.
How a list becomes a queue. Push one end, pop the other; BLPOP blocks for a worker.
What a set gives you for free. Uniqueness, O(1) membership tests, and server-side set
algebra.
What a sorted set adds. A score per member, kept ordered — leaderboards, priority queues, time-ordered feeds, range-by-score.
Setting a value and expiry together, and why. SET ... EX — so a crash between two commands
cannot leave the key without an expiry.
The command that can take Redis down. KEYS * — it blocks the single thread; use SCAN.
How to run several commands atomically. MULTI/EXEC, or a Lua script with EVAL.
Practice
SETa title andGETit back. ThenINCRa counter twice and confirm it is 2.- Have two shells
INCRthe same key many times and confirm no increments are lost. - Store a member as a hash and read one field with
HGET. ThenHINCRBYa numeric field. - Store the same member as a JSON string. List when each representation is better.
- Build a queue with
RPUSH/LPOP. Then tryBLPOPin one shell andRPUSHin another. - Add a duplicate to a set and confirm it appears once. Test membership with
SISMEMBER. - Build two sets and compute their intersection with
SINTER. - Build a leaderboard with
ZADD, read the top 3 withZREVRANGE, and bump one score withZINCRBY. - Use a sorted set as a time-ordered feed: score = timestamp, then
ZREVRANGEfor "most recent". - Set a key with
EXPIRE, watchTTLcount down, and confirm it disappears. - Set a value and its expiry in one
SET ... EXand explain the race it avoids. - Load a few thousand keys, then compare
KEYS *andSCAN. (On a toy instance only — never on production.)
Official documentation
- Redis — Data types — The overview of every type.
- Redis — Strings and Hashes — With
INCRandHINCRBY. - Redis — Lists — Queues and
BLPOP. - Redis — Sets and Sorted sets — Membership, set algebra, and leaderboards.
- Redis — Command reference — Every command, with its time complexity.
- Redis — SCAN — The safe alternative to
KEYS.
Next: the caching patterns, and the hard problem of invalidation.
Stuck on this lesson?
Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.
About the internship