iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Lists

Redis lists are linked-list-backed sequences of strings. LPUSH / RPUSH append, LPOP / RPOP remove, BLPOP blocks — everything you need for queues, stacks, and recent-N caches.

LPUSH/RPUSH, LRANGE, BLPOP, queues

EXAMPLE
# 1) Basics — left / right append + pop
RPUSH mylist a b c              # list = [a, b, c]
LPUSH mylist z                  # list = [z, a, b, c]
LPOP mylist                     # z (returns + removes from head)
RPOP mylist                     # c (from tail)

# 2) Read
LRANGE mylist 0 -1              # all elements (0 to last)
LRANGE mylist 0 9               # first 10
LRANGE mylist -10 -1            # last 10
LLEN mylist                      # length
LINDEX mylist 0                  # element at index

# 3) Modify by index / value
LSET mylist 0 'new-head'
LINSERT mylist BEFORE 'b' 'between'
LREM mylist 2 'foo'              # remove first 2 'foo' (negative count = from tail)

# 4) Trim — keep only first / last N (great for activity feeds)
LTRIM feed:user:42 0 99          # keep first 100

# 5) Blocking pop — wait for an item (for queue workers)
BLPOP queue:jobs 5               # wait up to 5 sec for an item
BRPOPLPUSH queue:jobs queue:in-flight 0   # atomic move (legacy)
BLMOVE queue:jobs queue:in-flight LEFT RIGHT 0   # modern, atomic

# 6) Right-pop + push to a second list — "reliable queue" pattern
# 1. Worker takes a job + moves it to in-flight in one atomic op
# 2. Processes the job
# 3. Removes it from in-flight on success
BRPOPLPUSH queue:jobs queue:worker-42-inflight 0

# 7) Atomic move between lists
LMOVE source destination LEFT RIGHT
# LEFT = from head, RIGHT = to tail (FIFO across the move)

# === Real recipes (Node + ioredis) ===

import Redis from 'ioredis';
const redis = new Redis();

# 8) Recent activity feed (bounded length)
async function pushActivity(userId, activity) {
    const key = `feed:user:${userId}`;
    await redis.multi()
        .lpush(key, JSON.stringify(activity))
        .ltrim(key, 0, 99)         // keep last 100
        .expire(key, 60 * 60 * 24 * 30)
        .exec();
}

async function getRecent(userId, n = 20) {
    const raw = await redis.lrange(`feed:user:${userId}`, 0, n - 1);
    return raw.map(JSON.parse);
}

# 9) Job queue (simple, fast, NOT durable)
async function enqueue(job) {
    await redis.rpush('queue:jobs', JSON.stringify(job));
}

async function worker() {
    while (true) {
        const [, raw] = await redis.blpop('queue:jobs', 30);    // 30s timeout
        if (!raw) continue;
        const job = JSON.parse(raw);
        try {
            await process(job);
        } catch (e) {
            await redis.rpush('queue:dead', JSON.stringify({ job, error: e.message }));
        }
    }
}

# 10) Reliable job queue — atomic move into in-flight
async function reliableWorker(name) {
    const flight = `queue:inflight:${name}`;
    while (true) {
        const raw = await redis.brpoplpush('queue:jobs', flight, 30);
        if (!raw) continue;
        const job = JSON.parse(raw);
        try {
            await process(job);
            await redis.lrem(flight, 1, raw);
        } catch (e) {
            await redis.lmove(flight, 'queue:dead', 'RIGHT', 'LEFT');
        }
    }
}

# Crashed workers' in-flight lists can be reconciled by a janitor:
# move items back to queue:jobs after a timeout.

# 11) Real-time chat ring buffer
async function appendChat(roomId, message) {
    const key = `room:${roomId}:chat`;
    await redis.rpush(key, JSON.stringify(message));
    await redis.ltrim(key, -500, -1);    // keep last 500
}

# 12) Stack pattern — LPUSH + LPOP
LPUSH undo-stack action1
LPUSH undo-stack action2
LPOP  undo-stack            # action2 (LIFO)

# 13) Performance + limits
# - List ops are O(1) at both ends
# - LINDEX, LSET, LREM by value are O(N) — avoid on long lists
# - Max list length: 2^32-1 elements
# - For at-least-once delivery + replay, prefer Streams (XADD/XREADGROUP)

# 14) When to pick lists vs other types
# Lists   : queues, recent-N, stacks, activity feeds
# Streams : durable queue with consumer groups, replay, IDs
# Pub/Sub : broadcast, no persistence
# Sorted set : priority queue, leaderboard, time-window
# Hash    : object with field-level updates

Why it matters

Lists shine for “cap to last N” feeds and simple job queues. The moment you need at-least-once delivery, retries, or replay, switch to Streams (XADD + consumer groups) — lists drop messages on crash.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
RPUSH queue "job1" "job2"
LPUSH queue "job0"
LRANGE queue 0 -1     # ["job0","job1","job2"]
LPOP queue            # "job0"
BRPOP queue 5          # blocking pop, 5s timeout
Try it Yourself »

Exercise

Push to the right end of a list.

jobs "job1"

Discussion

Loading…