Module F-3·24 min read

The full data structure surface and how each uses listpack vs ziplist vs skiplist vs hashtable encoding under the hood — what triggers encoding upgrades and how encoding determines memory usage.

JJS
Written by Jatin Jain Saraf · Senior Software Engineer

F-3 — Lists, Hashes, Sets, and Sorted Sets

Who this module is for: You know Redis has data structures beyond Strings — you have seen them in documentation — but you have never understood when to reach for each one, why they exist, or what is happening under the hood when you use them. This module teaches the full command surface and internal mechanics of Redis's four most important composite types. By the end, you will not just know the commands; you will know which structure to reach for before you start writing code.


Why Data Structures Matter in Redis

In F-2, you learned that a Redis String is not a string — it is a binary-safe byte array with encoding-aware storage. The same design philosophy applies to every Redis type: the shape of your data and the operations you need should determine which type you use.

This is different from how you might think about SQL. In PostgreSQL, you store everything in tables and columns, then join and aggregate at query time. In Redis, you choose the structure that makes your operations atomic and your memory efficient. The wrong choice does not cause a crash — it causes unnecessary complexity, non-atomic operations, and wasted RAM.

The four structures in this module — Lists, Hashes, Sets, and Sorted Sets — cover the vast majority of Redis use cases. Master these four and you can implement caching, queues, counters, leaderboards, sessions, pub/sub fan-out, social graphs, and rate limiters without reaching for anything else.


Lists

What a Redis List Is

A Redis List is an ordered sequence of strings, linked as a doubly-linked list (or, for small sizes, a listpack — more on this in the encoding section below). You can push elements to either end and pop elements from either end. This makes it both a stack (LIFO) and a queue (FIFO) depending on how you use it.

text

Core Commands

Pushing and popping:

text

Multi-element pop (count argument) was added in Redis 6.2. Before that, you had to call LPOP in a loop.

Inspecting without consuming:

text

LRANGE key 0 -1 returns all elements. Redis list indexes are zero-based, and negative indexes count from the tail: -1 is the last element, -2 is the second-to-last, and so on.

text

Blocking pop — the pattern that powers job queues:

text

BLPOP myqueue 0 blocks the client indefinitely until another client pushes to myqueue. The moment a push occurs, the blocked client is woken and the element is returned. This is how you build a job queue without polling:

text

The timeout argument is in seconds. 0 means block forever (until an element arrives or the connection is closed). A non-zero timeout returns (nil) if nothing arrives within the timeout — useful for workers that need to periodically check for shutdown signals.

BLPOP accepts multiple keys and returns from the first one that has data — this lets a single worker consume from multiple queues with a single connection.

Moving between lists atomically:

LMOVE source destination LEFT|RIGHT LEFT|RIGHT

LMOVE source destination RIGHT LEFT pops from the right of source and pushes to the left of destination atomically. This is the correct way to implement a reliable queue: move a job from pending to processing, and if the worker crashes, the job is still in processing and can be recovered.

The non-atomic alternative — RPOP from one list then LPUSH to another — has a window where the job is in neither list if your application crashes between the two commands.

Trimming a list to a fixed size:

LTRIM key start stop

After LTRIM mylog 0 999, only the first 1,000 elements remain. Use this after every LPUSH to cap a capped log or recent-activity feed.

When to Use a List

  • Job queue / task queue — RPUSH to enqueue, BLPOP to consume (FIFO)
  • Stack — LPUSH to push, LPOP to pop (LIFO)
  • Activity feed / recent events — LPUSH new events, LTRIM to keep the last N, LRANGE to read
  • Reliable queue — LMOVE from pending to processing, acknowledge by deleting from processing

Do not use a List when:

  • You need to look up elements by a specific value (no indexed lookup by content — use a Hash or Set)
  • You need uniqueness (use a Set)
  • You need ordering by score (use a Sorted Set)
  • The list grows to millions of elements and you iterate it frequently (consider Streams)

Hashes

What a Redis Hash Is

A Redis Hash is a map of field names to values — exactly what you might call a dictionary, object, or associative array in your programming language. Both fields and values are strings.

text

Core Commands

Setting fields:

text

In Redis 4.0+, HSET accepts multiple field-value pairs. The old HMSET command (which did the same thing) is deprecated. Use HSET.

Getting fields:

text

HGETALL returns a flat alternating list: field1, value1, field2, value2, ... Client libraries typically convert this to a map for you. For large hashes, be careful with HGETALL — it returns everything in one call and can be slow for hashes with thousands of fields.

Checking and deleting:

text

Incrementing numeric fields:

text

This is the right way to track per-user counters inside a Hash object:

text

Scanning large hashes:

HSCAN key cursor [MATCH pattern] [COUNT count]

Sign in to keep reading

The rest of this module is free — sign in with Google to unlock it and track your progress.

Sign in & Register

Discussion

0

Join the discussion

Loading comments...

© 2026 Jatin Jain Saraf (JJS). All rights reserved.