Redis Data Structures

Overview

Redis supports multiple data structures beyond simple key-value pairs, making it versatile for various use cases.

const redisDataStructures = {
  strings: 'Simple key-value pairs',
  hashes: 'Field-value pairs (objects)',
  lists: 'Ordered collections',
  sets: 'Unordered unique collections',
  sortedSets: 'Ordered unique collections with scores',
  bitmaps: 'Bit-level operations',
  hyperLogLogs: 'Cardinality estimation',
  streams: 'Append-only log',
  geospatial: 'Location data'
};

1. Strings

const redis = require('redis');
const client = redis.createClient();
await client.connect();

// Basic operations
await client.set('key', 'value');
const value = await client.get('key');

// Atomic operations
await client.incr('counter');
await client.incrBy('counter', 5);
await client.decr('counter');

// Bit operations
await client.setBit('flags', 0, 1);
const bit = await client.getBit('flags', 0);

// Use cases: caching, counters, flags

2. Hashes

// Store objects
await client.hSet('user:1', {
  name: 'John Doe',
  email: 'john@example.com',
  age: '30'
});

// Get all fields
const user = await client.hGetAll('user:1');

// Get specific fields
const name = await client.hGet('user:1', 'name');
const fields = await client.hmGet('user:1', ['name', 'email']);

// Increment numeric field
await client.hIncrBy('user:1', 'loginCount', 1);

// Check field exists
const exists = await client.hExists('user:1', 'email');

// Get all keys or values
const keys = await client.hKeys('user:1');
const values = await client.hVals('user:1');

// Use cases: user profiles, session data, object storage

3. Lists

// Queue (FIFO)
await client.rPush('queue', 'job1');
await client.rPush('queue', 'job2');
const job = await client.lPop('queue');  // job1

// Stack (LIFO)
await client.lPush('stack', 'item1');
await client.lPush('stack', 'item2');
const item = await client.lPop('stack');  // item2

// Get range
const items = await client.lRange('list', 0, -1);  // All items
const first10 = await client.lRange('list', 0, 9);

// Get by index
const first = await client.lIndex('list', 0);

// Insert before/after
await client.lInsert('list', 'BEFORE', 'pivot', 'newValue');

// Trim list
await client.lTrim('list', 0, 99);  // Keep first 100

// Blocking operations
const item = await client.blPop('queue', 5);  // Wait 5 seconds

// Use cases: queues, activity feeds, recent items

4. Sets

// Add members
await client.sAdd('tags:user:1', 'premium');
await client.sAdd('tags:user:1', 'verified');
await client.sAdd('tags:user:1', ['active', 'premium']);  // Duplicate ignored

// Get all members
const tags = await client.sMembers('tags:user:1');

// Check membership
const isMember = await client.sIsMember('tags:user:1', 'premium');

// Remove members
await client.sRem('tags:user:1', 'verified');

// Set operations
await client.sAdd('set1', ['a', 'b', 'c']);
await client.sAdd('set2', ['b', 'c', 'd']);

const union = await client.sUnion(['set1', 'set2']);      // [a,b,c,d]
const inter = await client.sInter(['set1', 'set2']);      // [b,c]
const diff = await client.sDiff(['set1', 'set2']);        // [a]

// Random member
const random = await client.sRandMember('tags:user:1');
const random3 = await client.sRandMemberCount('tags:user:1', 3);

// Pop random
const popped = await client.sPop('tags:user:1');

// Use cases: tags, unique visitors, social connections

5. Sorted Sets

// Add members with scores
await client.zAdd('leaderboard', [
  { score: 100, value: 'player1' },
  { score: 200, value: 'player2' },
  { score: 150, value: 'player3' }
]);

// Get by rank (ascending)
const bottom3 = await client.zRange('leaderboard', 0, 2);

// Get by rank (descending)
const top3 = await client.zRange('leaderboard', 0, 2, { REV: true });

// Get by score
const highScorers = await client.zRangeByScore('leaderboard', 150, 200);

// Get rank
const rank = await client.zRank('leaderboard', 'player1');
const revRank = await client.zRevRank('leaderboard', 'player1');

// Get score
const score = await client.zScore('leaderboard', 'player1');

// Increment score
await client.zIncrBy('leaderboard', 50, 'player1');

// Count by score range
const count = await client.zCount('leaderboard', 100, 200);

// Remove by rank or score
await client.zRemRangeByRank('leaderboard', 0, 2);
await client.zRemRangeByScore('leaderboard', 0, 100);

// Use cases: leaderboards, priority queues, time-series

6. Bitmaps

// Set bits
await client.setBit('online:2024-01-01', 123, 1);  // User 123 online
await client.setBit('online:2024-01-01', 456, 1);  // User 456 online

// Get bit
const isOnline = await client.getBit('online:2024-01-01', 123);

// Count set bits
const onlineCount = await client.bitCount('online:2024-01-01');

// Bitwise operations
await client.bitOp('AND', 'result', ['online:2024-01-01', 'online:2024-01-02']);

// Use cases: user activity tracking, feature flags

7. HyperLogLog

// Add elements
await client.pfAdd('unique:visitors', ['user1', 'user2', 'user3']);
await client.pfAdd('unique:visitors', ['user1', 'user4']);  // user1 counted once

// Get cardinality (approximate count)
const uniqueVisitors = await client.pfCount('unique:visitors');  // ~4

// Merge HyperLogLogs
await client.pfMerge('total:visitors', ['visitors:page1', 'visitors:page2']);

// Use cases: unique visitors, unique events (memory efficient)

8. Streams

// Add to stream
await client.xAdd('events', '*', {
  user: 'user1',
  action: 'login',
  timestamp: Date.now()
});

// Read from stream
const messages = await client.xRead([
  { key: 'events', id: '0' }
], { COUNT: 10 });

// Consumer groups
await client.xGroupCreate('events', 'processors', '0', { MKSTREAM: true });

// Read as consumer
const messages = await client.xReadGroup('processors', 'consumer1', [
  { key: 'events', id: '>' }
], { COUNT: 10 });

// Acknowledge message
await client.xAck('events', 'processors', messageId);

// Use cases: event sourcing, activity streams, message queues

9. Geospatial

// Add locations
await client.geoAdd('locations', [
  { longitude: -73.97, latitude: 40.77, member: 'Central Park' },
  { longitude: -73.98, latitude: 40.76, member: 'Times Square' }
]);

// Get coordinates
const coords = await client.geoPos('locations', 'Central Park');

// Calculate distance
const distance = await client.geoDist('locations', 'Central Park', 'Times Square', 'km');

// Find nearby
const nearby = await client.geoRadius('locations', -73.97, 40.77, 5, 'km');

// Find nearby by member
const nearCentralPark = await client.geoRadiusByMember('locations', 'Central Park', 5, 'km');

// Use cases: location-based services, nearby search

Real-World Examples

Session Store

class SessionStore {
  async createSession(userId, data) {
    const sessionId = generateId();
    await client.hSet(`session:${sessionId}`, {
      userId,
      ...data,
      createdAt: Date.now()
    });
    await client.expire(`session:${sessionId}`, 3600);
    return sessionId;
  }
  
  async getSession(sessionId) {
    return await client.hGetAll(`session:${sessionId}`);
  }
}

Rate Limiter

class RateLimiter {
  async checkLimit(userId, limit = 100, window = 60) {
    const key = `ratelimit:${userId}`;
    const now = Date.now();
    
    await client.zRemRangeByScore(key, 0, now - window * 1000);
    const count = await client.zCard(key);
    
    if (count >= limit) {
      return { allowed: false };
    }
    
    await client.zAdd(key, [{ score: now, value: `${now}` }]);
    await client.expire(key, window);
    
    return { allowed: true, remaining: limit - count - 1 };
  }
}

Leaderboard

class Leaderboard {
  async addScore(playerId, score) {
    await client.zAdd('leaderboard', [{ score, value: playerId }]);
  }
  
  async getTopPlayers(count = 10) {
    return await client.zRange('leaderboard', 0, count - 1, {
      REV: true,
      WITHSCORES: true
    });
  }
  
  async getPlayerRank(playerId) {
    return await client.zRevRank('leaderboard', playerId);
  }
}

.NET with Redis

using StackExchange.Redis;

public class RedisService
{
    private readonly IDatabase _db;
    
    // Hash operations
    public async Task SetHash(string key, Dictionary<string, string> values)
    {
        var entries = values.Select(kv => 
            new HashEntry(kv.Key, kv.Value)).ToArray();
        await _db.HashSetAsync(key, entries);
    }
    
    // List operations
    public async Task PushToList(string key, string value)
    {
        await _db.ListRightPushAsync(key, value);
    }
    
    // Set operations
    public async Task AddToSet(string key, string value)
    {
        await _db.SetAddAsync(key, value);
    }
    
    // Sorted set operations
    public async Task AddToSortedSet(string key, string member, double score)
    {
        await _db.SortedSetAddAsync(key, member, score);
    }
}

Interview Tips

  • Explain structures: Strings, hashes, lists, sets, sorted sets
  • Show use cases: When to use each structure
  • Demonstrate operations: Add, get, remove for each type
  • Discuss performance: O(1) for most operations
  • Mention advanced: Streams, HyperLogLog, geospatial
  • Show examples: Session store, rate limiter, leaderboard

Summary

Redis supports multiple data structures: strings (simple values), hashes (objects), lists (ordered), sets (unique), sorted sets (scored), bitmaps (bits), HyperLogLog (cardinality), streams (events), geospatial (locations). Choose structure based on use case. Most operations are O(1). Use hashes for objects, lists for queues, sets for unique items, sorted sets for rankings. Essential for building high-performance applications with Redis.

Test Your Knowledge

Take a quick quiz to test your understanding of this topic.

Test Your Nosql Knowledge

Ready to put your skills to the test? Take our interactive Nosql quiz and get instant feedback on your answers.