Topic 1.3
Lists: Queues, Recent Items, and Reliable Handoff
In one line
A list is an ordered sequence you push and pop at both ends in O(1), with blocking pops for worker queues. It's perfect for recent-items feeds and simple job queues, but a plain pop loses the job if the worker crashes, so reliable queues need LMOVE or Streams.
Think of it like this
A spike on a restaurant order rail. Waiters push tickets on one end, cooks take them from the other. If a cook takes a ticket and then faints, that ticket is gone unless the cook first moved it to a "cooking now" rail that someone checks.
Key ideas
- 01
Commands:
LPUSH/RPUSH(add to head/tail),LPOP/RPOP(with an optional count since 6.2),LRANGE(O(S+N)),LLEN,LINDEXandLSET(O(N) in the middle),LINSERT,LREM,LTRIM,LPOS(6.0.6),LMOVE(6.2, replacesRPOPLPUSH),LMPOP(7.0, pop from the first non-empty of several lists). - 02
Blocking commands:
BLPOP,BRPOP,BLMOVE,BLMPOP. A worker callsBRPOP queue 5; if the list is empty, the connection waits up to 5 seconds without polling, and Redis wakes the longest-waiting client when an item arrives. Each blocked worker holds a connection, so use dedicated connections for blocking calls. - 03
Encoding: lists are quicklists, a doubly linked list of listpack nodes (
list-max-listpack-size -2means ~8 KB per node). Push and pop at the ends are O(1); access by index in the middle is O(N). - 04
Capped "recent items":
LPUSH recent:user:123 itemthenLTRIM recent:user:123 0 49keeps the last 50. Pipeline both, or put them inMULTI, so the list never grows unbounded. - 05
The reliability problem:
BRPOPremoves the job the moment it's delivered. If the worker crashes before finishing, the job is lost. The reliable queue pattern usesBLMOVE queue processing:{worker} RIGHT LEFTso the job moves atomically into a per-worker processing list, and the workerLREMs it after success. A reaper moves jobs from dead workers' lists back to the queue. - 06
Even with
LMOVE, lists lack consumer groups, acknowledgements, delivery counts, and replay. For anything beyond simple queues, Redis Streams (Phase 5) provide those. For durable, high-volume queues, consider SQS, RabbitMQ or Kafka.
Code & diagrams
# Producer
127.0.0.1:6379> LPUSH jobs:email '{"id":"j1","to":"a@x.com"}'
(integer) 1
# Worker (blocks up to 5s if empty)
127.0.0.1:6379> BRPOP jobs:email 5
1) "jobs:email"
2) "{\"id\":\"j1\",\"to\":\"a@x.com\"}"
# Reliable variant: atomically move into this worker's processing list
127.0.0.1:6379> LPUSH jobs:email '{"id":"j2"}'
127.0.0.1:6379> BLMOVE jobs:email jobs:email:processing:w1 RIGHT LEFT 5
"{\"id\":\"j2\"}"
# ... do the work ...
127.0.0.1:6379> LREM jobs:email:processing:w1 1 '{"id":"j2"}'
(integer) 1
# Recent items, capped at 50
127.0.0.1:6379> LPUSH recent:user:123 "product:9"
127.0.0.1:6379> LTRIM recent:user:123 0 49Interview problem
The problem
Background job queue with Redis lists
Build an email job queue: producers push jobs, workers process them. Walk through producer failure, worker failure, duplicate processing, lost messages, retries, dead letters and poison messages. Then explain when you'd move to Redis Streams.
You're given
- 10K jobs/min
- Workers can crash at any time
- Emails must not be silently lost
- Some jobs fail forever (bad address)
The interviewer follows up
Is a Redis list queue durable?
How do you get priority queues with lists?
When it breaks
Workers use plain BRPOP and are killed during deploys
What you see
Every in-flight job at deploy time disappears. Customers don't get emails and there's no trace.
Fix & prevent
Use BLMOVE into a processing list plus a reaper, or Streams with consumer groups; drain workers gracefully on SIGTERM.
A "recent activity" list without LTRIM
What you see
The list grows forever; LRANGE 0 -1 in the app becomes slower each week until it's a big-key incident.
Fix & prevent
Always pair LPUSH with LTRIM in a pipeline or transaction; read with bounded ranges.
Explain it without notes
Why can a job be lost with BRPOP, and how does BLMOVE fix it?
Why do blocking commands need their own connections in a pooled client?
Practice
Build a "last 10 searches per user" feature with no duplicates and a cap of 10.
Write the reaper logic in pseudocode for the reliable queue.
Trade-offs
- ↔
Lists: simplest queue, O(1), but reliability features are hand-built. Streams: consumer groups, acks and replay built in, with more concepts to learn.
- ↔
Redis queues give very low latency but bounded durability; brokers like SQS and Kafka give durability and retention at higher latency and cost.
Done when you can
I can build a basic and a reliable queue with lists.
I can explain at-least-once delivery, visibility timeouts and dead-letter queues in list terms.
I know when to move from lists to Streams or a real broker.