Redis Data Structures and How to Use Them
The five core Redis types and the specialized ones, organized by internal encoding, operation complexity, and practical use
Redis offers different ways to store a value, split by type. Picking the type that matches the problem shrinks both the code and the memory footprint.
An identity as a data structure server
Redis is an in-memory data store that keeps everything in memory. Latency is lower than a disk-based database, and every command executes atomically. The official documentation defines Redis as a data structure server.
That definition is what separates it from an ordinary key-value cache. Instead of reading and writing whole values, the server itself performs operations such as appending an element to a list or intersecting two sets. That removes round trips where the application downloads a large value, transforms it, and writes it back.
Throughput depends on CPU, networking, value size, and pipelining. Command execution is mostly serialized, so a long command can delay requests behind it. I/O and background work can use separate threads; Redis as a whole is not single-threaded.
The five core types
String, Hash, List, Set, and Sorted Set are the types used most often. Each one is good at a specific set of operations.
| Type | Structure | Strong operations | Typical use |
|---|---|---|---|
| String | single value | GET/SET, INCR | cached values, counters |
| Hash | field-value map | HSET/HGET | object profiles |
| List | ordered deque | LPUSH/BRPOP | job queues, timelines |
| Set | set without duplicates | SADD, SINTER | tags, deduplication |
| Sorted Set | set ordered by score | ZADD, ZRANGE | real-time leaderboards |
The "strong operations" column is the criterion for choosing a type. Even for the same data, the type follows from which queries are frequent.
String is the most basic type and can hold up to 512MB under the default bulk-length limit. Integer values support atomic increment, which makes it a good fit for counters.
SET page:home "<html>" -> OK
INCR pv:home -> 1 (starts from 0 when absent)
SETEX session:abc 60 "uid42" -> stored with a 60-second TTLSETEX on the last line stores a value together with an expiry. TTL stands for Time To Live, the lifetime after which the key is deleted automatically.
Hash groups multiple fields of an object under one key. It saves memory compared to giving each field its own String key.
HSET user:1001 name "Alice" age 25
HGET user:1001 name -> "Alice"
HINCRBY user:1001 age 1 -> 26
HGETALL user:1001 -> all field-value pairsStoring the same data as a Hash instead of individual Strings can cut memory substantially (roughly 50% to 90% depending on the data, estimated). The more small objects involved, the larger the difference.
List is an ordered collection with O(1) push and pop at both ends. Set is a collection without duplicates where membership checks are O(1).
LPUSH jobs "task1" push a job to the front of the queue
BRPOP jobs 5 pop from the back, wait up to 5 seconds if empty
SADD tags:post1 "redis" "db" add tags without duplicates
SINTER tags:post1 tags:post2 tags shared by two posts (intersection)BRPOP is a blocking command that waits the given number of seconds when no element is available. That single line builds a job queue without any polling.
Sorted Set assigns a score to each member and keeps them ordered automatically. It suits leaderboards, where sorting and rank lookups are frequent.
ZADD rank 100 "Alice" 120 "Bob"
ZREVRANGE rank 0 2 WITHSCORES top 3 with their scores
ZRANK rank "Bob" ascending rank of "Bob"
ZINCRBY rank 5 "Alice" increase "Alice" score by 5Changing a score updates the internal position immediately, so updates and rank lookups need no separate index.
Internal encoding and memory
Redis changes the internal representation of a type depending on size. Small collections go into a compact memory block, and larger ones switch to a hash table or a skip list.
| Type | Compact encoding | Larger representation |
|---|---|---|
| String | int / embstr | raw |
| Hash | listpack | hashtable |
| List | listpack | quicklist |
| Set | intset / listpack | hashtable |
| Sorted Set | listpack | skiplist |
Small Hashes and Lists use listpack, which stores entries in contiguous memory. It replaced ziplist with a different encoding; larger Lists use quicklist to link multiple listpacks. Small integer-only Sets use intset, a sorted integer array.
The following output comes from running the earlier commands on Redis 8.10.1 with default settings in 2026-09. A small Sorted Set also uses listpack.
OBJECT ENCODING user:1001 -> "listpack"
OBJECT ENCODING rank -> "listpack"The conversion thresholds are configurable. A redis.conf example looks like this.
hash-max-listpack-entries 512
hash-max-listpack-value 64
list-max-listpack-size -2A Hash switches to hashtable beyond 512 fields or when a field name or value exceeds 64 bytes. list-max-listpack-size -2 sets an 8KiB limit per List node; positive 8192 means entries, not bytes. The Redis configuration file defines these units.
Probabilistic and specialized types
Beyond the five core types there are types built for specific problems. They come into play when a little accuracy can be traded away, or when the domain involves something like coordinates.
Stream is an append-only log structure. Consumer groups and acknowledgements (ACK) let several workers split the message load.
XADD events * type "login" uid "42" auto-generate the ID
XGROUP CREATE events g1 0 create a consumer group
XREADGROUP GROUP g1 c1 STREAMS events > read only new messagesBitmap is an optimized string that represents one user's state in a single bit. For on/off counting such as daily login tracking, it uses very little memory.
SETBIT login:2026-06-30 42 1 mark that user 42 logged in today
BITCOUNT login:2026-06-30 number of users who logged in todayHyperLogLog is a probabilistic structure that estimates cardinality. It records only the hash distribution rather than storing every element.
PFADD uv:home "u1" "u2" "u3"
PFCOUNT uv:home estimated number of unique visitorsThe Redis documentation gives a standard error of about 0.81%. Dense representation uses about 12KB, while small sets can use less through sparse representation. This suits Unique Visitor (UV) counting when memory matters more than exact cardinality.
Geospatial places coordinates on top of a Sorted Set and provides distance and radius search.
GEOADD stores 126.97 37.56 "Store A"
GEOSEARCH stores FROMLONLAT 126.98 37.55 BYRADIUS 5 km ASCGEOSEARCH returns members within the radius of a reference coordinate, nearest first. Internally it is a Sorted Set whose score encodes the coordinate, so location search works with no additional index.
How to choose a type
Even for the same data, the type follows from which queries you issue most often. The flow below maps query shape to type.
Including the specialized types, the problem-to-type mapping is as follows.
| Problem | Type | Key commands |
|---|---|---|
| Real-time leaderboard | Sorted Set | ZADD / ZREVRANGE |
| Job queue | List | LPUSH / BRPOP |
| Unique visitor estimation | HyperLogLog | PFADD / PFCOUNT |
| Daily activity tracking | Bitmap | SETBIT / BITCOUNT |
| Location-based search | Geospatial | GEOADD / GEOSEARCH |
| Event stream | Stream | XADD / XREADGROUP |
If ordering is central, Sorted Set fits; if an exact count is unnecessary and memory matters, HyperLogLog does. The deciding factor is the shape of the frequent queries, not the storage volume.
Summary
Redis types differ in the operations they support and the encodings they use. Choose a type by frequent queries, such as ranking, field updates, or event consumption.
Compact encodings can save memory, but their thresholds and representations depend on the Redis version. Check actual values with OBJECT ENCODING and measure latency under the intended workload.