Redis Lists

Lists are linked lists of string values, supporting push/pop operations from both ends in O(1) time. They're ideal for queues, stacks, activity feeds, and any scenario requiring ordered data with fast insertion.

When Redis Lists Are the Right Choice

Choose Redis lists when insertion order matters and you mainly push or pop from the ends. They work well for simple worker queues, recent activity feeds, undo stacks, and capped message buffers.

Key Characteristics

• Maximum length: 2^32 - 1 elements (over 4 billion) • O(1) push/pop at head or tail • O(N) access by index (linked list behavior) • Supports blocking operations for pub/sub patterns

Basic Push/Pop Operations

# Push to list
RPUSH queue:jobs "job1" "job2" "job3"   # Add to right (tail)
LPUSH queue:jobs "job0"                 # Add to left (head)

# Pop from list
RPOP queue:jobs                         # Remove from right → "job3"
LPOP queue:jobs                         # Remove from left → "job0"

# Pop multiple
LPOP queue:jobs 2                       # Pop 2 from left
RPOP queue:jobs 3                       # Pop 3 from right

Range Operations

LRANGE queue:jobs 0 -1                  # Get all elements
LRANGE queue:jobs 0 2                   # Get first 3 elements
LRANGE queue:jobs -3 -1                 # Get last 3 elements
LLEN queue:jobs                         # Get list length

Queue Pattern (FIFO)

First In, First Out: add to tail, remove from head.

# Enqueue
RPUSH queue:tasks "task1"
RPUSH queue:tasks "task2"
RPUSH queue:tasks "task3"

# Dequeue
LPOP queue:tasks                        # → "task1"
LPOP queue:tasks                        # → "task2"

Stack Pattern (LIFO)

Last In, First Out: add to head, remove from head.

# Push
LPUSH stack:undo "action1"
LPUSH stack:undo "action2"
LPUSH stack:undo "action3"

# Pop
LPOP stack:undo                         # → "action3"
LPOP stack:undo                         # → "action2"

Blocking Operations

Block until an element is available—perfect for worker queues.

# Block up to 30 seconds for an item
BRPOP queue:jobs 30

# Block on multiple queues (priority order)
BLPOP queue:priority queue:normal 0     # 0 = block forever

# Block and move atomically
BLMOVE source dest LEFT RIGHT 30        # Move with 30s timeout

Index Operations

LINDEX queue:jobs 0                     # Get element at index
LINDEX queue:jobs -1                    # Get last element
LSET queue:jobs 0 "updated_job"         # Update element at index
LPOS queue:jobs "target_value"          # Find position of value

Insert and Remove

# Insert relative to element
LINSERT queue:jobs BEFORE "job2" "job1.5"
LINSERT queue:jobs AFTER "job2" "job2.5"

# Remove occurrences
LREM queue:jobs 1 "job1"                # Remove 1 occurrence
LREM queue:jobs 0 "job1"                # Remove all occurrences
LREM queue:jobs -2 "job1"               # Remove 2 from tail

Trimming

Keep only a range of elements—perfect for capped collections.

# Keep only last 100 notifications
LPUSH notifications:user:1 "new notification"
LTRIM notifications:user:1 0 99

# Activity feed with automatic capping
def add_activity(user_id, activity):
    key = f"feed:user:{user_id}"
    LPUSH(key, activity)
    LTRIM(key, 0, 999)  # Keep only 1000 most recent

Moving Between Lists

# Atomically move element between lists
LMOVE source dest LEFT RIGHT            # Pop from source left, push to dest right
LMOVE source dest RIGHT LEFT            # Pop from source right, push to dest left

# Rotate within same list
LMOVE mylist mylist LEFT RIGHT          # Move head to tail

Use Case: Reliable Queue

Use LMOVE for reliable processing with acknowledgment.

# Producer
RPUSH queue:pending "job_data"

# Worker
while True:
    # Move job to processing list atomically
    job = LMOVE queue:pending queue:processing RIGHT LEFT

    if job:
        try:
            process(job)
            # Remove from processing on success
            LREM queue:processing 1 job
        except Exception:
            # Re-queue on failure
            LMOVE queue:processing queue:pending LEFT RIGHT

Use Case: Recent Activity Feed

def add_activity(user_id, activity_json):
    key = f"activity:{user_id}"

    # Add to feed
    LPUSH(key, activity_json)

    # Keep only last 50 items
    LTRIM(key, 0, 49)

    # Optional: set expiration for inactive users
    EXPIRE(key, 86400 * 30)  # 30 days

def get_recent_activity(user_id, count=20):
    key = f"activity:{user_id}"
    return LRANGE(key, 0, count - 1)

Common Redis List Mistakes

A common mistake is using lists as a general random-access array. Redis lists are optimized for the ends, not arbitrary index reads or updates, so performance drops if the workload constantly pokes the middle of long lists.

Related

Knowledge is power.