This episode covers the next two fundamental data structures: Lists for queues and task queues with blocking operations, and Hashes for storing objects like user profiles and session data with field-level access.

Episode 3 equipped you with Strings. Now we level up with two structures that solve real everyday problems: Lists for queues and Hashes for objects.
Lists are Redis's answer to queues and timelines — and with blocking operations, Lists can even become a simple message queue. Hashes are the representation of objects: user profiles, session data, configuration — all fit naturally as a field-value map. You'll use these two structures almost every day. Let's get straight to practice.
A List in Redis is a linked list that holds an ordered sequence of elements. The core operations: LPUSH/RPUSH to add on the left/right, LPOP/RPOP to remove from the left/right.
redis-cli RPUSH notifications "pesan-1"
redis-cli RPUSH notifications "pesan-2"
redis-cli LPOP notifications
redis-cli LLEN notificationsRPUSH notifications "pesan-1" appends on the right, LPOP notifications takes from the left — the classic FIFO queue combination. LLEN gives the list length. This RPUSH + LPOP pattern is the foundation of the simplest task queue.
To view a list's contents without deleting them, use LRANGE — a must-remember command because it's the only way to see the whole list:
redis-cli LRANGE notifications 0 -1
redis-cli LINDEX notifications 0
redis-cli LTRIM notifications 0 4LRANGE notifications 0 -1 reads all elements (index -1 means the rightmost). LINDEX accesses an element by index. LTRIM trims the list keeping only a given range — a technique used to cap the length of logs or timelines.
Danger
Be careful with LRANGE notifications 0 -1 on a huge list: reading millions of elements at once blocks the server. For large lists, always read page by page or move to another structure. Details are covered in episode 17.
This is the power of Lists as a message queue. BLPOP/BRPOP wait with a timeout if the list is empty — workers no longer need to poll the list and can be activated the moment data arrives:
redis-cli BLPOP task_queue 5BLPOP task_queue 5 waits at most 5 seconds until an element appears in task_queue, then takes it. If nothing arrives, the command returns nil. This prevents busy-waiting and enables an efficient worker architecture — we'll reinforce it with Streams in episode 6.
A Hash is a field → value map. Unlike a JSON string that must be serialized in full, a Hash allows per-field access — saving bandwidth and speeding up partial updates:
redis-cli HSET user:1 name "Arman" email "arman@example.com" role "admin"
redis-cli HGET user:1 name
redis-cli HGETALL user:1HSET user:1 name "Arman" email "arman@example.com" role "admin" creates one object with three fields. HGET reads a single field, HGETALL reads them all. This is the standard pattern for storing a user profile.
redis-cli HMGET user:1 name email
redis-cli HDEL user:1 role
redis-cli HEXISTS user:1 name
redis-cli HKEYS user:1
redis-cli HLEN user:1HMGET reads several fields at once, HDEL deletes one field, HEXISTS checks whether a field exists, HKEYS lists all field names, and HLEN counts the fields. Note that HGETALL/HKEYS on a large hash carries the same blocking risk as LRANGE on a large list.
Hashes also have per-field atomic counters — great for profiles with statistics:
redis-cli HINCRBY user:1 visit_count 1
redis-cli HINCRBY user:1 visit_count 1HINCRBY user:1 visit_count 1 increments the visit_count field atomically. Each call adds one — the end result is 2 after two calls. This is an example of field-level atomic operations that no other data type has.
Combining a Hash with a TTL yields the perfect session store:
redis-cli HSET session:9f3a userId 123 role "admin"
redis-cli EXPIRE session:9f3a 1800HSET session:9f3a userId 123 role "admin" stores a session as an object, then EXPIRE session:9f3a 1800 gives it a 30-minute lifetime. Compared with storing a session as a JSON string, a Hash is more efficient: the application only needs to read the role field without pulling the whole payload.
| Need | Choice |
|---|---|
| Store object, access some fields | Hash |
| Store complex nested document | String containing JSON |
| Cache a full API response | String |
| Session with expiry | Hash + TTL |
| Counter per object attribute | Hash + HINCRBY |
Rule of thumb: if the application frequently reads/updates a single field, use a Hash. If an object is always read and written whole as one unit, a JSON String is simpler.
Episode 4 equipped you with Lists for queues with blocking operations and Hashes for objects with field-level access: LPUSH/RPOP, LRANGE, BLPOP/BRPOP, then HSET/HGETALL, HMGET, HINCRBY, and the Hash + TTL session store pattern.
Key takeaways:
RPUSH + LPOP forms a FIFO queue.LRANGE 0 -1 for reading; LTRIM to cap the list length.BLPOP/BRPOP wait for new data with a timeout — efficient for workers.HGET per-field is more efficient than a whole JSON.HINCRBY provides per-field atomic counters.HSET + EXPIRE.In the next episode, episode 5, we cover Sets, Sorted Sets & HyperLogLog — structures for unique collections. Sets for tags and set operations, Sorted Sets for leaderboards and priority queues, and HyperLogLog for counting unique visitors with a constant 12KB of memory. Let's continue!