Redis is more than a plain "key → value" string store. It provides several built-in data structures, and choosing the right one directly affects how simple your code is and how fast it runs.
Why choosing the right structure matters
Imagine you store a user profile as a single JSON string. To update one field — the age — you read the whole string, parse the JSON, change the field, serialize it back, and write it again. That's five steps instead of one.
A Hash lets you update a single field directly with HSET user:42 age 31. Less code, less traffic, fewer mistakes.
Each Redis structure solves its own class of problems. Let's go through them one by one.
String — strings and counters
String is the base Redis type. The value can be a plain string, a number, JSON, or a binary blob (for example, compressed data). The maximum size is 512 MB.
SET user:42:name "Alexey"
GET user:42:name # → "Alexey"
SET page:views 0
INCR page:views # → 1
INCR page:views # → 2
INCRBY page:views 10 # → 12
In short: INCR / INCRBY are atomic operations with no risk of a race condition, handy for counters and rate limiters.
Typical uses of String:
- caching an HTML fragment or API response;
- a view or like counter;
- a session token or a one-time confirmation code.
TTL and key expiration
Any Redis key can be made temporary with a TTL (time to live). The key will be removed automatically once it expires.
SET session:abc123 "user_data"
EXPIRE session:abc123 3600 # expires in 1 hour
# set the TTL right when writing:
SET otp:phone:79001234567 "8814" EX 300 # lives for 300 seconds
TTL session:abc123 # → remaining seconds (-2 = already gone)
PERSIST session:abc123 # remove the TTL, make it permanent
Redis uses two mechanisms to evict expired keys:
- Passive expiration — the key is checked and removed the moment it is accessed.
- Active expiration — a background process periodically scans a sample of keys and removes the expired ones.
This means expired keys don't disappear instantly — they can still "live" in memory for a few more seconds until the next background pass.
Hash — an object by fields
A Hash stores a set of "field → value" pairs under a single key. It's a perfect fit for objects with many attributes.
HSET product:10 name "Laptop" price 89990 stock 15
HGET product:10 price # → "89990"
HGETALL product:10 # → all fields and values
HINCRBY product:10 stock -1 # decrease the stock by 1
HDEL product:10 stock # delete a single field
The advantage of a Hash over a JSON string: you change one field without reading the whole object. With many fields, that difference is noticeable.
Typical uses of Hash:
- a user profile (name, email, role, registration date);
- a shopping cart (product → quantity);
- application configuration.
List — queues and stacks
A List is a two-sided queue of strings. It supports adding and reading from both ends.
LPUSH tasks "task-1" # add to the head
RPUSH tasks "task-2" # add to the tail
LPOP tasks # take from the head → "task-1"
RPOP tasks # take from the tail → "task-2"
LLEN tasks # list length
LRANGE tasks 0 9 # first 10 elements
In short: LPUSH + RPOP = a FIFO queue; LPUSH + LPOP = a LIFO stack.
The blocking variants BLPOP / BRPOP wait for an element to appear — a simple replacement for a broker in uncomplicated scenarios:
BLPOP tasks 5 # wait for an element up to 5 seconds, then return nil
Typical uses of List:
- a task queue (background jobs);
- a feed of recent events (with
LTRIMto cap the length); - a user's action history.
Set — unique elements
A Set stores an unordered collection of unique strings. Duplicates are ignored automatically.
SADD tags:post:5 "java" "redis" "backend"
SADD tags:post:5 "redis" # duplicate — ignored
SMEMBERS tags:post:5 # → {"java", "redis", "backend"}
SISMEMBER tags:post:5 "java" # → 1 (present) / 0 (absent)
SCARD tags:post:5 # → 3 (set size)
# Operations over several sets:
SINTER tags:post:5 tags:post:7 # intersection
SUNION tags:post:5 tags:post:7 # union
SDIFF tags:post:5 tags:post:7 # difference
Typical uses of Set:
- a list of unique page visitors;
- article tags;
- a list of friends or followers (intersection = mutual friends).
Sorted Set — leaderboards and ranges
A Sorted Set is like a Set, but each element carries a numeric score (weight). Elements are kept sorted by score — that's the key property.
ZADD leaderboard 1500 "alice"
ZADD leaderboard 2300 "bob"
ZADD leaderboard 1800 "carol"
ZRANGE leaderboard 0 -1 WITHSCORES # all, from lowest to highest
ZREVRANGE leaderboard 0 2 # top 3, from highest to lowest
ZSCORE leaderboard "alice" # → "1500"
ZRANK leaderboard "alice" # position (0-based) ascending
ZREVRANK leaderboard "bob" # position in reverse order → 0 (1st)
ZINCRBY leaderboard 200 "alice" # add 200 to alice's score
Typical uses of Sorted Set:
- a game leaderboard (score = points);
- a priority queue (score = priority or timestamp);
- an event history with lookup by time range.
Bitmap, HyperLogLog and Streams — briefly
These structures solve specific problems:
Bitmap — a bit array on top of a String. Each bit is addressed by an offset. It's used to record boolean facts compactly across a large number of entities.
SETBIT active_users:2024-06-01 42 1 # user 42 was active
GETBIT active_users:2024-06-01 42 # → 1
BITCOUNT active_users:2024-06-01 # number of active users that day
HyperLogLog — a probabilistic structure for counting unique elements. It takes at most 12 KB regardless of the number of elements, but gives an approximate result (error ~0.81%).
PFADD visitors:2024-06-01 "user-1" "user-2" "user-3"
PFCOUNT visitors:2024-06-01 # approximate number of unique elements
Streams — a structure for event streams, close in model to Kafka. Each entry has a unique ID and a set of fields. It supports consumer groups and read-with-acknowledgment.
XADD events * action "click" user_id "42" # add an event
XREAD COUNT 10 STREAMS events 0 # read the last 10
Streams are a good fit for an audit log, passing events between services, and a simple broker inside a single Redis.
Which structure to choose
| Task | Structure |
|---|---|
| Cache of an arbitrary value or a counter | String |
| Object with named fields | Hash |
| Queue or stack | List |
| Collection of unique values | Set |
| Leaderboard, priority queue | Sorted Set |
| Activity of millions of entities | Bitmap |
| Count of unique elements (approximate) | HyperLogLog |
| Event stream with consumer groups | Streams |
In short
- Redis provides seven built-in data structures — String, Hash, List, Set, Sorted Set, Bitmap, HyperLogLog, Streams.
- String works both as a string and as an atomic counter (
INCR/INCRBY). - Hash lets you update individual fields of an object without rewriting the whole value.
- List implements queues (FIFO) and stacks (LIFO);
BLPOPmakes a queue blocking. - Set stores unique elements and supports intersection, union, and difference operations.
- Sorted Set adds a numeric score to a Set — elements are always sorted.
- Any key can be made temporary with
EXPIRE— Redis will remove it automatically.
What to read next
- Redis fundamentals: how it works and why you need it
- Caching patterns: cache-aside, write-through, TTL
- Redis beyond caching: queues, locks, pub/sub