Skip to main content

Redis Data Types & Value Types

Redis supports 10 distinct data types, each with specific encoding strategies and use cases. Understanding which type to use — and how Redis encodes it internally — is critical for performance and memory optimization.


1. String

The most fundamental type. A Redis String is a binary-safe byte sequence — not just text. It can hold:

  • Text, JSON
  • Integers (with atomic increment/decrement)
  • Binary data (images, serialized objects)
  • Max size: 512 MB
SET user:1 "Alice"                     # Simple string
SET counter 0
INCR counter # Atomic increment → 1
INCRBY counter 10 # → 11
INCRBYFLOAT price 1.50 # Float increment

SETNX lock "owner" # Set if Not eXists (atomic CAS)
SET lock "owner" NX EX 30 # Set + TTL in one command (preferred)

GETSET key newvalue # Atomic get-and-set (deprecated: use GETDEL + SET)
MSET k1 v1 k2 v2 k3 v3 # Set multiple (not atomic across keys)
MGET k1 k2 k3 # Get multiple (single round-trip)

Internal Encoding

ValueEncodingMemory
Integer ≤ 2^63intShared integer pool for 0–9999
String ≤ 44 bytesembstrSingle allocation, immutable
String > 44 bytesrawTwo allocations (robj + SDS)
OBJECT ENCODING mykey   # See which encoding Redis chose

Senior insight: embstr is immutable — any modification to a string ≤44 bytes promotes it to raw. If you frequently APPEND to a key, it immediately becomes raw, wasting the embstr optimization.


2. Hash

A Redis Hash is a dictionary of field→value pairs within a single key. Perfect for representing objects without deserializing the entire value.

HSET user:1 name "Alice" email "alice@example.com" age 30
HGET user:1 name # "Alice"
HMGET user:1 name email # Multiple fields, single round-trip
HGETALL user:1 # All fields (O(n) — avoid on large hashes)
HINCRBY user:1 age 1 # Atomic field increment
HSETNX user:1 email "new@example.com" # Set field if not exists
HDEL user:1 email # Delete field
HEXISTS user:1 name # Check field existence

# Scan through hash fields (avoid HGETALL on huge hashes)
HSCAN user:1 0 MATCH "*" COUNT 100

Hash vs String Serialization Trade-offs

ApproachProsCons
SET user:1 "{json}"Single GET, simpleMust deserialize everything; can't update one field
HSET user:1 field valAtomic per-field update; partial readsHGETALL is O(n); more commands for complex objects

Memory optimization: Keep hashes under the hash-max-listpack-entries threshold (128 by default) to use the compact listpack encoding — dramatically lower memory than a full hash table.

hash-max-listpack-entries 128   # Use listpack up to 128 fields
hash-max-listpack-value 64 # Use listpack if each value ≤ 64 bytes

3. List

A doubly-linked list (internally quicklist — a linked list of listpack nodes). Supports O(1) push/pop from both ends.

RPUSH queue "task1" "task2" "task3"     # Push to tail (queue producer)
LPOP queue # Pop from head (queue consumer) → "task1"
RPOPLPUSH queue processing # Atomic: pop from queue, push to processing list
LRANGE queue 0 -1 # Get all elements (0 to last)
LLEN queue # List length
LINDEX queue 0 # Get element by index (O(n))

# Blocking operations — essential for queue patterns
BLPOP queue 30 # Block up to 30 seconds waiting for an element
BRPOPLPUSH queue dead-letter 30 # Blocking reliable queue pattern

List as Queue vs Stack

# Queue (FIFO):
RPUSH queue item # Producer: push to tail
LPOP queue # Consumer: pop from head

# Stack (LIFO):
LPUSH stack item # Push to head
LPOP stack # Pop from head

Reliable Queue Pattern

# Consumer atomically moves item to processing list
RPOPLPUSH messages:pending messages:processing

# After successful processing:
LREM messages:processing 1 "task-id"

# Recovery: items in messages:processing without heartbeat are re-queued
# This prevents message loss if consumer crashes

Internal encoding: quicklist = doubly-linked list of listpack nodes. Each node holds up to list-max-listpack-size entries (default: -2 = up to 8 KB per node). This balances memory (compact listpack) with O(1) head/tail operations.


4. Set

An unordered collection of unique strings. Uses hash table internally (or listpack/intset for small sets).

SADD tags "java" "backend" "redis"
SISMEMBER tags "java" # O(1) membership check
SCARD tags # Size → 3
SMEMBERS tags # All members (avoid on large sets)
SRANDMEMBER tags 2 # 2 random members (no removal)
SPOP tags # Remove and return random member

# Set operations — great for social graphs, feature flags
SINTER set1 set2 # Intersection
SUNION set1 set2 # Union
SDIFF set1 set2 # Difference (in set1 but not set2)
SINTERSTORE dest set1 set2 # Store intersection result

Use Cases

Use CasePattern
Unique visitorsSADD visitors:2024-01-15 user:123
Tags / labelsSADD article:42:tags "redis" "backend"
Friend listsSINTERSTORE mutual user:1:friends user:2:friends
Permission flagsSADD user:1:permissions "READ" "WRITE"
DeduplicationSADD processed:emails "email-hash"

intset encoding: If a Set contains only integers and has ≤ 512 members, Redis uses a compact sorted integer array instead of a hash table — 5–10x more memory efficient.


5. Sorted Set (ZSet)

The most powerful Redis type — a Set where every member has a score (float). Members are stored in score order. Backed by both a skip list (for ordered operations) and a hash table (for O(1) score lookup).

ZADD leaderboard 1500.5 "alice"
ZADD leaderboard 2300 "bob" 1200 "charlie"
ZSCORE leaderboard "alice" # Get score → 1500.5
ZINCRBY leaderboard 100 "alice" # Increment score atomically
ZRANK leaderboard "alice" # Rank (0-indexed, lowest score first) → 1
ZREVRANK leaderboard "alice" # Rank from highest score → 1

ZRANGE leaderboard 0 2 # Top 3 by score ascending
ZREVRANGE leaderboard 0 2 # Top 3 by score descending
ZRANGEBYSCORE leaderboard 1000 2000 # Members with score 1000–2000
ZRANGEBYSCORE leaderboard -inf +inf WITHSCORES LIMIT 0 10 # Paginated

ZREM leaderboard "charlie"
ZCARD leaderboard # Number of members
ZCOUNT leaderboard 1000 2000 # Count members in score range

Advanced Sorted Set Patterns

# Rate limiting with Sorted Set (sliding window)
ZADD user:1:requests (timestamp) (timestamp) # Add request timestamp as score
ZREMRANGEBYSCORE user:1:requests -inf (now - window) # Remove old entries
ZCARD user:1:requests # Count recent requests

# Priority queue (lower score = higher priority)
ZADD pq 1 "urgent-task"
ZADD pq 10 "low-priority-task"
ZPOPMIN pq # Pop lowest score (highest priority)

# Leaderboard with tie-breaking
ZADD board 1000 "alice_user:1" # Secondary sort by lexicographic member name
ZRANGEBYLEX board "[" "+" LIMIT 0 10 # When all scores equal, sort lexicographically

6. Bitmap

Not a distinct type — bitmaps are stored as Strings but operated on at the bit level. Extremely memory-efficient for boolean per-user data.

SETBIT user:active:20240115 1234 1      # User 1234 was active on Jan 15
GETBIT user:active:20240115 1234 # → 1
BITCOUNT user:active:20240115 # Count active users on Jan 15

# Bitwise operations across bitmaps
BITOP AND dest active:jan active:feb # Users active both months
BITOP OR dest active:jan active:feb # Users active in either month
BITOP XOR dest active:jan active:feb # Users active in one but not both

BITPOS user:active:20240115 1 # First active user ID
BITPOS user:active:20240115 0 # First inactive user ID

Memory Efficiency

Tracking 1 million users' daily activity:

  • Without Bitmap: 1M hashes or strings → ~100 MB
  • With Bitmap: 1M bits = 125 KB (800x compression)

Use case: Daily active user (DAU) tracking, feature flag rollouts (bit = user has feature), attendance systems.


7. HyperLogLog

A probabilistic data structure for counting unique items with fixed memory (~12 KB) regardless of the number of unique elements. Error rate: ~0.81%.

PFADD page:/home visitor:123 visitor:456 visitor:789
PFADD page:/home visitor:123 # Duplicate — not counted
PFCOUNT page:/home # Approximate unique count → 3

# Merge multiple HyperLogLogs
PFMERGE total page:/home page:/about page:/products
PFCOUNT total # Approximate total unique visitors across all pages

When to use: When you need cardinality estimates and can tolerate ~1% error — analytics dashboards, unique visitor counts, distinct search queries. Not for exact counts — use a Set for that.


8. Geospatial

Stored internally as a Sorted Set with encoded coordinates as scores (using Geohash encoding).

GEOADD restaurants 103.8198 1.3521 "Hawker Centre"    # longitude, latitude, name
GEOADD restaurants 103.8554 1.2800 "Marina Bay Sands"

GEODIST restaurants "Hawker Centre" "Marina Bay Sands" km # → ~8.2 km

# Find restaurants within 5 km of a point
GEOSEARCH restaurants FROMMEMBER "Hawker Centre" BYRADIUS 5 km ASC
GEOSEARCH restaurants FROMLONLAT 103.85 1.28 BYRADIUS 5 km ASC COUNT 10 WITHCOORD WITHDIST

GEOPOS restaurants "Hawker Centre" # Get stored coordinates back
GEOHASH restaurants "Hawker Centre" # Geohash string

Senior note: Geospatial data is stored in a Sorted Set — you can use all ZSet commands (ZRANGE, ZREM, etc.) on geospatial keys. Precision is approximately 0.0001°, good to ~11 meters.


9. Stream

A log-like data structure for append-only sequences of messages. Combines the best of Kafka-style streams with Redis simplicity. [→ See dedicated redis-streams.md]


10. Type Comparison Summary

TypeBest ForTime ComplexityMemory
StringSingle values, counters, cacheO(1) all opsLowest
HashObjects with many fieldsO(1) per field, O(n) for allLow (listpack)
ListQueues, stacks, timelinesO(1) head/tail, O(n) middleMedium
SetUnique items, membership, set opsO(1) add/check, O(n) opsMedium
Sorted SetLeaderboards, ranges, priority queuesO(log N) add, O(log N + M) rangeHighest
BitmapPer-user boolean trackingO(1) per bit, O(n) for BITCOUNTMinimal
HyperLogLogApproximate cardinalityO(1) all opsFixed ~12 KB
GeospatialLocation-based queriesO(log N) add, O(N+M log M) searchLike Sorted Set
StreamEvent streaming, message queuesO(1) add, O(log N) read by IDModerate