Data Types

Redis provides several data types optimized for different use cases. Here's when and how to use each.

Quick Reference#

TypeUse CaseExample
StringSimple values, counters, JSONSession data, cache
HashObjects with fieldsUser profiles, settings
ListOrdered collections, queuesMessage queues, feeds
SetUnique collectionsTags, followers
Sorted SetRanked dataLeaderboards, timelines
StreamEvent logsActivity feeds, messaging

Strings#

The simplest type. Can hold text, numbers, or binary data up to 512MB.

Common Commands#

TypeScript
// SET - store a value await redis.set('key', 'value'); await redis.set('key', 'value', 'EX', 3600); // Expires in 1 hour // GET - retrieve a value const value = await redis.get('key'); // INCR/DECR - atomic counters await redis.incr('counter'); await redis.incrby('counter', 10); await redis.decr('counter'); // MGET/MSET - multiple keys await redis.mset('key1', 'val1', 'key2', 'val2'); const [v1, v2] = await redis.mget('key1', 'key2'); // SETNX - set only if not exists const wasSet = await redis.setnx('lock', 'holder'); // SETEX - set with expiry await redis.setex('session', 3600, 'data');

Use Cases#

  • Caching: Store serialized JSON, HTML fragments
  • Counters: Page views, rate limits
  • Locks: Distributed locking with SETNX
  • Sessions: Store session data with expiry

Memory#

  • Overhead: ~90 bytes per key
  • Values up to 512MB (but keep them small)
  • Use compression for large values

Hashes#

Key-value pairs within a key. Perfect for objects.

Common Commands#

TypeScript
// HSET - set field(s) await redis.hset('user:123', 'name', 'Alice'); await redis.hset('user:123', { name: 'Alice', email: 'alice@example.com', age: '30' }); // HGET - get a field const name = await redis.hget('user:123', 'name'); // HGETALL - get all fields const user = await redis.hgetall('user:123'); // { name: 'Alice', email: 'alice@example.com', age: '30' } // HMGET - get multiple fields const [name, email] = await redis.hmget('user:123', 'name', 'email'); // HINCRBY - increment numeric field await redis.hincrby('user:123', 'loginCount', 1); // HDEL - delete field(s) await redis.hdel('user:123', 'temporaryField'); // HEXISTS - check if field exists const exists = await redis.hexists('user:123', 'name'); // HKEYS/HVALS - get all keys or values const fields = await redis.hkeys('user:123'); const values = await redis.hvals('user:123');

Use Cases#

  • User profiles: Store user data as fields
  • Settings: Application or user configuration
  • Objects: Any entity with multiple properties
  • Counters by category: stats:daily {views: 100, clicks: 50}

Memory#

  • More efficient than separate string keys for related data
  • ~100 bytes overhead per hash + ~50 bytes per field
  • Use for objects with < 100 fields

Lists#

Ordered sequences. Add/remove from both ends.

Common Commands#

TypeScript
// LPUSH/RPUSH - add to left/right await redis.lpush('queue', 'item1'); // Add to front await redis.rpush('queue', 'item2'); // Add to back // LPOP/RPOP - remove from left/right const item = await redis.lpop('queue'); const item = await redis.rpop('queue'); // BRPOP/BLPOP - blocking pop (for queues) const [key, value] = await redis.brpop('queue', 30); // Wait up to 30s // LRANGE - get range of elements const items = await redis.lrange('queue', 0, -1); // All items const recent = await redis.lrange('feed', 0, 9); // First 10 // LLEN - get length const length = await redis.llen('queue'); // LINDEX - get element by index const item = await redis.lindex('queue', 0); // LTRIM - keep only a range await redis.ltrim('recent', 0, 99); // Keep first 100

Use Cases#

  • Message queues: Producer/consumer pattern
  • Activity feeds: Recent activity, latest posts
  • Stacks: LIFO with LPUSH/LPOP
  • Bounded logs: Use LTRIM to cap size

Memory#

  • ~40 bytes per element + element size
  • Use for ordered data where you access by position
  • Not efficient for random access by value

Sets#

Unordered collections of unique strings.

Common Commands#

TypeScript
// SADD - add member(s) await redis.sadd('tags:article:1', 'redis', 'database', 'nosql'); // SREM - remove member(s) await redis.srem('tags:article:1', 'nosql'); // SMEMBERS - get all members const tags = await redis.smembers('tags:article:1'); // SISMEMBER - check membership const hasTag = await redis.sismember('tags:article:1', 'redis'); // SCARD - count members const count = await redis.scard('tags:article:1'); // SINTER - intersection of sets const common = await redis.sinter('user:1:friends', 'user:2:friends'); // SUNION - union of sets const all = await redis.sunion('set1', 'set2'); // SDIFF - difference of sets const unique = await redis.sdiff('user:1:friends', 'user:2:friends'); // SRANDMEMBER - random member(s) const random = await redis.srandmember('users', 5); // SPOP - remove and return random const winner = await redis.spop('raffle');

Use Cases#

  • Tags: Unique tags per item
  • Followers/Following: User relationships
  • Unique visitors: Track unique IPs per day
  • Recommendations: Find common interests

Memory#

  • ~80 bytes per member + member size
  • Deduplication is automatic
  • Efficient for membership tests

Sorted Sets#

Like sets, but each member has a score for ordering.

Common Commands#

TypeScript
// ZADD - add with score await redis.zadd('leaderboard', 100, 'player1'); await redis.zadd('leaderboard', { player1: 100, player2: 200 }); // ZSCORE - get score const score = await redis.zscore('leaderboard', 'player1'); // ZINCRBY - increment score await redis.zincrby('leaderboard', 10, 'player1'); // ZRANK/ZREVRANK - get rank (0-indexed) const rank = await redis.zrevrank('leaderboard', 'player1'); // Highest first // ZRANGE/ZREVRANGE - get by rank const top10 = await redis.zrevrange('leaderboard', 0, 9, 'WITHSCORES'); // ZRANGEBYSCORE - get by score range const highScorers = await redis.zrangebyscore('leaderboard', 100, '+inf'); // ZREM - remove member await redis.zrem('leaderboard', 'player1'); // ZCARD - count members const count = await redis.zcard('leaderboard'); // ZCOUNT - count in score range const elite = await redis.zcount('leaderboard', 1000, '+inf');

Use Cases#

  • Leaderboards: Rank by score
  • Priority queues: Process by priority
  • Time-based data: Score = timestamp
  • Rate limiting: Sliding window counters
  • Autocomplete: Score = popularity

Memory#

  • ~80 bytes per member + member size + 8 bytes for score
  • O(log N) operations
  • Best for ranked/sorted data

Streams#

Append-only log data structure. Ideal for event sourcing.

Common Commands#

TypeScript
// XADD - add entry const id = await redis.xadd('events', '*', 'type', 'click', 'userId', '123'); // XREAD - read entries const entries = await redis.xread('COUNT', 10, 'STREAMS', 'events', '0'); // XREAD BLOCK - blocking read const entries = await redis.xread('BLOCK', 5000, 'STREAMS', 'events', '$'); // XRANGE - get range by ID const entries = await redis.xrange('events', '-', '+', 'COUNT', 100); // XLEN - count entries const count = await redis.xlen('events'); // Consumer Groups await redis.xgroup('CREATE', 'events', 'mygroup', '$', 'MKSTREAM'); // Read as consumer const entries = await redis.xreadgroup( 'GROUP', 'mygroup', 'consumer1', 'COUNT', 10, 'STREAMS', 'events', '>' ); // Acknowledge processing await redis.xack('events', 'mygroup', entryId);

Use Cases#

  • Event sourcing: Immutable event log
  • Message queues: With consumer groups
  • Activity streams: Chronological events
  • Notifications: Multi-consumer delivery

Memory#

  • ~100 bytes per entry + field data
  • Entries can be trimmed by count or ID
  • Consumer groups track delivery state

Choosing the Right Type#

NeedUse
Simple key-valueString
Object with fieldsHash
Queue (FIFO)List
Unique itemsSet
Ranked itemsSorted Set
Event logStream
CounterString (INCR)
Time seriesSorted Set (score=timestamp)
Session dataHash or String (JSON)
RelationshipsSet (intersections, unions)