Data Types and Commands
Strings, hashes, lists, sets, and sorted sets with real commands, including a leaderboard example.
Redis's value lies in its data structures — every key stores a value of one specific type, and each type has its own dedicated set of commands built for that structure. This page covers the five you'll use most.
Strings
The simplest type — a key mapped to a single value (text, a number, or serialized binary data).
SET user:42:name "Amara Diallo"
GET user:42:name
# "Amara Diallo"
SET page:views 0
INCR page:views # atomic increment, returns the new value
INCRBY page:views 10 # increment by a specific amount
SET session:abc123 "active" EX 1800 # expires automatically after 1800 seconds
TTL session:abc123 # seconds remaining, or -1 if no expiry
EXPIRE session:abc123 3600 # (re)set an expiry on an existing key
INCR/INCRBY are atomic — safe for concurrent callers incrementing the same counter (a view count, a rate-limit counter) without a race condition, something that would otherwise require a manual read-modify-write with locking.
Hashes
A hash maps multiple field/value pairs under one key — a natural fit for an object with several attributes, like a user profile, without serializing it into a single string blob:
HSET user:42 name "Amara Diallo" email "amara@example.com" country "SN"
HGET user:42 email
# "amara@example.com"
HGETALL user:42
# 1) "name"
# 2) "Amara Diallo"
# 3) "email"
# 4) "amara@example.com"
# 5) "country"
# 6) "SN"
HINCRBY user:42 loginCount 1 # atomic increment on a single hash field
The advantage over storing the whole object as one JSON string in a plain SET: individual fields can be read or updated (HGET, HSET, HINCRBY) without touching the rest of the object.
Lists
A list is an ordered collection of values, implemented as a linked list — fast to push/pop from either end, making it a natural fit for queues and recent-activity feeds:
LPUSH recent:signups "user:44" # push onto the left (head)
LPUSH recent:signups "user:45"
RPOP recent:signups # pop from the right (tail) -> "user:44"
LRANGE recent:signups 0 9 # first 10 elements (0-indexed, inclusive)
LLEN recent:signups # number of elements
LPUSH + RPOP (or RPUSH + LPOP) implements a simple FIFO queue directly with two commands — a common lightweight pattern before reaching for a dedicated message broker.
Sets
A set is an unordered collection of unique values — useful for membership checks and set algebra (union, intersection, difference):
SADD product:1:tags "electronics" "accessories" "wireless"
SISMEMBER product:1:tags "wireless" # 1 (true) or 0 (false)
SMEMBERS product:1:tags # all members, unordered
SCARD product:1:tags # number of members
SADD viewed:userA "p1" "p2" "p3"
SADD viewed:userB "p2" "p3" "p4"
SINTER viewed:userA viewed:userB # products both users viewed: p2, p3
SISMEMBER is O(1) — checking "is this tag present" doesn't require scanning the whole set, unlike checking membership in a list.
Sorted sets
A sorted set is like a set (unique members) but each member has an associated numeric score, and the set is kept ordered by that score automatically — this is Redis's dedicated tool for rankings and leaderboards.
ZADD leaderboard 1500 "player:amara"
ZADD leaderboard 2200 "player:liam"
ZADD leaderboard 1800 "player:priya"
ZRANGE leaderboard 0 2 WITHSCORES REV # top 3, highest score first
# 1) "player:liam"
# 2) "2200"
# 3) "player:priya"
# 4) "1800"
# 5) "player:amara"
# 6) "1500"
ZINCRBY leaderboard 50 "player:amara" # amara scores 50 more points -> 1550
ZRANK leaderboard "player:amara" # amara's rank (0-indexed, ascending by default)
ZSCORE leaderboard "player:liam" # liam's current score -> "2200"
Every one of those operations — insert with a score, get the top N, increment a score, look up a specific member's rank — runs in logarithmic time even as the leaderboard grows to millions of entries, because a sorted set is internally backed by a skip list plus a hash table for O(1) score lookups by member.
Choosing the right type
| Need | Type |
|---|---|
| A single value, a flag, an atomic counter | String |
| An object with several fields, updated individually | Hash |
| An ordered queue or recent-items feed | List |
| Unique membership, set operations (union/intersect) | Set |
| A ranked list ordered by a score | Sorted set |
Common mistakes
- Storing a whole object as a single JSON string when a hash would let individual fields be read or updated directly, without deserializing and re-serializing the entire object for a one-field change.
- Using
LRANGE key 0 -1(the whole list) on a list that's expected to grow very large, rather than paginating with bounded ranges — the operation's cost scales with the range requested. - Reaching for a sorted set's score as if it were arbitrary metadata rather than the thing the set is ordered by — trying to filter sorted sets by anything other than score or rank requires pulling data out and filtering in the application instead.
- Forgetting
EXPIRE/EXentirely on keys that should be temporary (sessions, rate-limit counters, cache entries) — without it, a key persists in memory indefinitely.