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)

Java (Spring Data Redis)​

@Autowired
private StringRedisTemplate stringRedisTemplate;

public void stringExamples() {
ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
ops.set("user:1:name", "Alice");
ops.set("session:abc", "data", Duration.ofHours(1));

String name = ops.get("user:1:name");
Long views = ops.increment("page:views");
}

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 "[email protected]" 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 "[email protected]" # 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

Java (Spring Data Redis)​

public void hashExamples() {
HashOperations<String, String, String> hashOps = redisTemplate.opsForHash();

Map<String, String> user = new HashMap<>();
user.put("name", "Alice");
user.put("age", "30");
hashOps.putAll("user:1", user);

String name = hashOps.get("user:1", "name");
Map<String, String> allFields = hashOps.entries("user:1");
}

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

Java (Spring Data Redis)​

public void listExamples() {
ListOperations<String, String> listOps = redisTemplate.opsForList();

listOps.rightPush("queue:jobs", "job1");
listOps.rightPush("queue:jobs", "job2");

String job = listOps.leftPop("queue:jobs");
List<String> all = listOps.range("queue:jobs", 0, -1);
}

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

Java (Spring Data Redis)​

public void setExamples() {
SetOperations<String, String> setOps = redisTemplate.opsForSet();

setOps.add("tags:post:1", "redis", "java", "backend");
Boolean isMember = setOps.isMember("tags:post:1", "redis");
Set<String> members = setOps.members("tags:post:1");
}

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

Java (Spring Data Redis)​

public void sortedSetExamples() {
ZSetOperations<String, String> zsetOps = redisTemplate.opsForZSet();

zsetOps.add("leaderboard", "alice", 1500);
zsetOps.add("leaderboard", "bob", 2300);

Set<ZSetOperations.TypedTuple<String>> top3 =
zsetOps.reverseRangeWithScores("leaderboard", 0, 2);
}

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

Interview Questions​

Q: How do you select the right Redis data type for a new feature?​

A: Start from access pattern and operation complexity, then optimize for memory and consistency needs.

Q: Why are Sorted Sets common in senior interview scenarios?​

A: They model rank, range queries, and priority semantics efficiently with predictable performance.

Q: When should HyperLogLog be avoided?​

A: When exact distinct counts are required for billing, quotas, or compliance logic.

Q: What is a key risk when using Hash for object storage?​

A: Unbounded field growth can make full reads expensive and increase memory unexpectedly.

Q: How do hash tags help cluster-safe multi-key operations?​

A: They force related keys into the same slot so multi-key commands remain valid.

Q: Why should teams care about internal encodings like listpack or embstr?​

A: Encoding choices directly affect memory footprint and command latency under scale.