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
Contents
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.
Read and write throughput is high as well. The official Redis benchmark uses 50 clients, a 256-byte payload, and Redis Open Source 8.x. Under those conditions, String GET and SET handle roughly 170,000 to 220,000 operations per second. The single-threaded design means no contention between commands.
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. 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.
| Data type | Redis 6.2 and earlier | Redis 7.0 and later |
|---|---|---|
| String | raw / int | raw / int |
| Hash (small) | ziplist | listpack |
| List (small) | ziplist | listpack |
| List (general) | quicklist | quicklist |
| Set (integers only) | intset | intset |
| Sorted Set | skiplist | skiplist |
Small Hashes and Lists live in a listpack (formerly called ziplist), a single contiguous block of memory. With no pointer overhead, it saves memory. A Set containing only integers uses intset, a sorted integer array, which is more compact still.
Check the actual encoding with the OBJECT ENCODING command.
OBJECT ENCODING user:1001 -> "listpack" or "hashtable"
OBJECT ENCODING rank -> "skiplist"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 8192A Hash switches to hashtable once it exceeds 512 fields or holds a value longer than 64 bytes. Staying on listpack keeps memory small, but operations slow down on large collections, so set the thresholds to match object size.
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 visitorsPer the official Redis documentation, one HyperLogLog uses a fixed 12KB and has a standard error of about 0.81%. Memory does not grow even when counting hundreds of millions of items, which fits Unique Visitor (UV) counting where an exact number is not required.
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 is a data structure server, and each type differs in the operations it is good at and in its internal encoding. The five core types cover most caching and queueing needs. Problems such as ranking, unique counting, and location search hand off to specialized types like Sorted Set, HyperLogLog, and Geospatial. Internal encoding switches automatically with size, so grouping small objects into a Hash keeps them on listpack encoding and saves memory. Checking the actual representation with OBJECT ENCODING before shipping a new feature prevents production surprises.