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).

Bash
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:

Bash
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:

Bash
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):

Bash
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.

Bash
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/EX entirely on keys that should be temporary (sessions, rate-limit counters, cache entries) — without it, a key persists in memory indefinitely.