← Back to the section

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 LTRIM to 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

TaskStructure
Cache of an arbitrary value or a counterString
Object with named fieldsHash
Queue or stackList
Collection of unique valuesSet
Leaderboard, priority queueSorted Set
Activity of millions of entitiesBitmap
Count of unique elements (approximate)HyperLogLog
Event stream with consumer groupsStreams

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); BLPOP makes 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.
  • Redis fundamentals: how it works and why you need it
  • Caching patterns: cache-aside, write-through, TTL
  • Redis beyond caching: queues, locks, pub/sub