VALKEY-LISTS(7)
π valkey-lists(7) β the Lists page, bestie π
okay so welcome to the valkey-lists manual page from the Valkey Manual, section 7 π it's all Lists, all the time, and honestly? iconic β¨
πΈ NAME πΈ
valkey-lists β Lists π¦
β¨ DESCRIPTION β¨
Lists are linked lists of string values. π§ that's the whole vibe. And bestie, people use Lists literally all the time to:
- Implement stacks and queues. π₯
- Build queue management for background worker systems. π·ββοΈ
π Basic commands π
LPUSHβ adds a new element to the head of a list;RPUSHadds to the tail. π«ΆLPOPβ removes and returns an element from the head of a list;RPOPdoes the same but from the tails of a list. πLLENβ returns the length of a list. πLMOVEβ atomically moves elements from one list to another. πLTRIMβ reduces a list to the specified range of elements. βοΈ
π«Ά Blocking commands π«Ά
Lists support several blocking commands. For example:
BLPOPβ removes and returns an element from the head of a list. If the list is empty, the command blocks until an element becomes available or until the specified timeout is reached. β³BLMOVEβ atomically moves elements from a source list to a target list. If the source list is empty, the command will block until a new element becomes available. π΄
See the valkey-commands(7) complete series of list commands. π
π₯ EXAMPLES π₯
- Treat a list like a queue (first in, first out): ποΈ
127.0.0.1:6379> LPUSH bikes:repairs bike:1
(integer) 1
127.0.0.1:6379> LPUSH bikes:repairs bike:2
(integer) 2
127.0.0.1:6379> RPOP bikes:repairs
"bike:1"
127.0.0.1:6379> RPOP bikes:repairs
"bike:2"
- Treat a list like a stack (first in, last out): π₯
127.0.0.1:6379> LPUSH bikes:repairs bike:1
(integer) 1
127.0.0.1:6379> LPUSH bikes:repairs bike:2
(integer) 2
127.0.0.1:6379> LPOP bikes:repairs
"bike:2"
127.0.0.1:6379> LPOP bikes:repairs
"bike:1"
- Check the length of a list: π
127.0.0.1:6379> LLEN bikes:repairs
(integer) 0
- Atomically pop an element from one list and push to another: π
127.0.0.1:6379> LPUSH bikes:repairs bike:1
(integer) 1
127.0.0.1:6379> LPUSH bikes:repairs bike:2
(integer) 2
127.0.0.1:6379> LMOVE bikes:repairs bikes:finished LEFT LEFT
"bike:2"
127.0.0.1:6379> LRANGE bikes:repairs 0 -1
1) "bike:1"
127.0.0.1:6379> LRANGE bikes:finished 0 -1
1) "bike:2"
- To limit the length of a list you can call
LTRIM: βοΈ
127.0.0.1:6379> RPUSH bikes:repairs bike:1 bike:2 bike:3 bike:4 bike:5
(integer) 5
127.0.0.1:6379> LTRIM bikes:repairs 0 2
OK
127.0.0.1:6379> LRANGE bikes:repairs 0 -1
1) "bike:1"
2) "bike:2"
3) "bike:3"
π§ What are Lists? π§
To explain the List data type it's better to start with a little bit of theory, because the term List is often used in an improper way by information technology folks. π For instance "Python Lists" are not what the name may suggest (Linked Lists), but rather Arrays (the same data type is called Array in Ruby actually).
From a very general point of view a List is just a sequence of ordered elements: 10,20,1,2,3 is a list. π« But the properties of a List implemented using an Array are very different from the properties of a List implemented using a Linked List.
Lists are implemented via Linked Lists. This means that even if you have millions of elements inside a list, the operation of adding a new element in the head or in the tail of the list is performed in constant time. β‘ The speed of adding a new element with the LPUSH command to the head of a list with ten elements is the same as adding an element to the head of list with 10 million elements. no cap.
What's the downside? π¬ Accessing an element by index is very fast in lists implemented with an Array (constant time indexed access) and not so fast in lists implemented by linked lists (where the operation requires an amount of work proportional to the index of the accessed element).
Lists are implemented with linked lists because for a database system it is crucial to be able to add elements to a very long list in a very fast way. πββοΈ Another strong advantage, as you'll see in a moment, is that Lists can be taken at constant length in constant time.
When fast access to the middle of a large collection of elements is important, there is a different data structure that can be used, called sorted sets. π― Sorted sets are covered in the valkey-sorted-sets(7) tutorial page.
π First steps with Lists π
The LPUSH command adds a new element into a list, on the left (at the head), while the RPUSH command adds a new element into a list, on the right (at the tail). Finally the LRANGE command extracts ranges of elements from lists:
127.0.0.1:6379> RPUSH bikes:repairs bike:1
(integer) 1
127.0.0.1:6379> RPUSH bikes:repairs bike:2
(integer) 2
127.0.0.1:6379> LPUSH bikes:repairs bike:important_bike
(integer) 3
127.0.0.1:6379> LRANGE bikes:repairs 0 -1
1) "bike:important_bike"
2) "bike:1"
3) "bike:2"
Note that LRANGE takes two indexes, the first and the last element of the range to return. Both the indexes can be negative, telling Valkey to start counting from the end: so -1 is the last element, -2 is the penultimate element of the list, and so forth. π’
As you can see RPUSH appended the elements on the right of the list, while the final LPUSH appended the element on the left. π«Ά
Both commands are variadic commands, meaning that you are free to push multiple elements into a list in a single call:
127.0.0.1:6379> RPUSH bikes:repairs bike:1 bike:2 bike:3
(integer) 3
127.0.0.1:6379> LPUSH bikes:repairs bike:important_bike bike:very_important_bike
127.0.0.1:6379> LRANGE bikes:repairs 0 -1
1) "bike:very_important_bike"
2) "bike:important_bike"
3) "bike:1"
4) "bike:2"
5) "bike:3"
An important operation defined on Lists is the ability to pop elements. Popping elements is the operation of both retrieving the element from the list, and eliminating it from the list, at the same time. π£ You can pop elements from left and right, similarly to how you can push elements in both sides of the list. We'll add three elements and pop three elements, so at the end of this sequence of commands the list is empty and there are no more elements to pop:
127.0.0.1:6379> RPUSH bikes:repairs bike:1 bike:2 bike:3
(integer) 3
127.0.0.1:6379> RPOP bikes:repairs
"bike:3"
127.0.0.1:6379> LPOP bikes:repairs
"bike:1"
127.0.0.1:6379> RPOP bikes:repairs
"bike:2"
127.0.0.1:6379> RPOP bikes:repairs
(nil)
Valkey returned a NULL value to signal that there are no elements in the list. π
π¦ Common use cases for lists π¦
Lists are useful for a number of tasks, two very representative use cases are the following:
- Remember the latest updates posted by users into a social network. π±
- Communication between processes, using a consumer-producer pattern where the producer pushes items into a list, and a consumer (usually a worker) consumes those items and executes actions. Valkey has special list commands to make this use case both more reliable and efficient. π
For example both the popular Ruby libraries resque and sidekiq use Lists under the hood in order to implement background jobs. π
The popular Twitter social network takes the latest tweets posted by users into Lists. π¦
To describe a common use case step by step, imagine your home page shows the latest photos published in a photo sharing social network and you want to speedup access. πΈ
- Every time a user posts a new photo, we add its ID into a list with
LPUSH. - When users visit the home page, we use
LRANGE 0 9in order to get the latest 10 posted items.
π Capped lists π
In many use cases we just want to use lists to store the latest items, whatever they are: social network updates, logs, or anything else. ποΈ
Valkey allows us to use lists as a capped collection, only remembering the latest N items and discarding all the oldest items using the LTRIM command. βοΈ
The LTRIM command is similar to LRANGE, but instead of displaying the specified range of elements it sets this range as the new list value. All the elements outside the given range are removed. π§Ή
For example, if you're adding bikes on the end of a list of repairs, but only want to worry about the 3 that have been on the list the longest:
127.0.0.1:6379> RPUSH bikes:repairs bike:1 bike:2 bike:3 bike:4 bike:5
(integer) 5
127.0.0.1:6379> LTRIM bikes:repairs 0 2
OK
127.0.0.1:6379> LRANGE bikes:repairs 0 -1
1) "bike:1"
2) "bike:2"
3) "bike:3"
The above LTRIM command tells Valkey to keep just list elements from index 0 to 2, everything else will be discarded. This allows for a very simple but useful pattern: doing a List push operation + a List trim operation together to add a new element and discard elements exceeding a limit. Using LTRIM with negative indexes can then be used to keep only the 3 most recently added:
127.0.0.1:6379> RPUSH bikes:repairs bike:1 bike:2 bike:3 bike:4 bike:5
(integer) 5
127.0.0.1:6379> LTRIM bikes:repairs -3 -1
OK
127.0.0.1:6379> LRANGE bikes:repairs 0 -1
1) "bike:3"
2) "bike:4"
3) "bike:5"
The above combination adds new elements and keeps only the 3 newest elements into the list. With LRANGE you can access the top items without any need to remember very old data. π
Note: while LRANGE is technically an O(N) command, accessing small ranges towards the head or the tail of the list is a constant time operation. π«Ά
π Blocking operations on lists π
Lists have a special feature that make them suitable to implement queues, and in general as a building block for inter process communication systems: blocking operations. π§
Imagine you want to push items into a list with one process, and use a different process in order to actually do some kind of work with those items. This is the usual producer / consumer setup, and can be implemented in the following simple way:
- To push items into the list, producers call
LPUSH. π₯ - To extract / process items from the list, consumers call
RPOP. π€
However it is possible that sometimes the list is empty and there is nothing to process, so RPOP just returns NULL. In this case a consumer is forced to wait some time and retry again with RPOP. This is called polling, and is not a good idea in this context because it has several drawbacks:
- Forces Valkey and clients to process useless commands (all the requests when the list is empty will get no actual work done, they'll just return NULL). π
- It adds a delay to how fast items get processed, because once a worker gets a
NULLback it just... sits there for a bit. You could make that delay smaller by waiting less between calls toRPOPβ but bestie, that just makes problem number 1 worse, aka even more useless calls to Valkey. Not the move. π
So Valkey ships commands called BRPOP and BLPOP, which are basically RPOP and LPOP but they can block when the list is empty. They only come back to whoever called them when a new element gets added to the list, or when a timeout you specified runs out. That's it. That's the whole vibe. β¨
Here's an example of a BRPOP call we could use in the worker:
127.0.0.1:6379> RPUSH bikes:repairs bike:1 bike:2
(integer) 2
127.0.0.1:6379> BRPOP bikes:repairs 1
1) "bikes:repairs"
2) "bike:2"
127.0.0.1:6379> BRPOP bikes:repairs 1
1) "bikes:repairs"
2) "bike:1"
127.0.0.1:6379> BRPOP bikes:repairs 1
(nil)
(2.01s)
Which is literally saying: βwait for elements in the list bikes:repairs, but dip out if after 1 second there's still nothing available.β π¦
Heads up: you can pass 0 as the timeout to wait for elements forever (main character patience, honestly), and you can also hand it multiple lists instead of just one, so you're waiting on a whole group of lists at the same time and get pinged the second the first one gets an element. π
A few things to know about BRPOP:
- Clients get served in an ordered way: the first client that blocked waiting on a list gets served first when some other client pushes an element, and so on down the line. First come, first served, no cutting. π
- The return value is different from
RPOPβ it's a two-element array, because it also includes the name of the key. Makes sense!BRPOPandBLPOPcan block waiting on elements from multiple lists, so you gotta know which list answered. π - If the timeout is reached,
NULLis returned. Simple as.
There's more you should know about lists and blocking ops, so we suggest you go read up on these:
- You can build safer queues or rotating queues using
LMOVE. - There's also a blocking version of that command, called
BLMOVE.
π Automatic creation and removal of keys π
Okay so this whole time, in all our examples, we never once had to create an empty list before pushing elements into it, and we never had to remove an empty list once everything inside was gone. That's because it's Valkey's job: it deletes keys when lists are left empty, and it creates an empty list if the key doesn't exist yet and we're trying to add elements to it β with LPUSH, for example. π«Ά
And this isn't a lists-only thing, bestie. It applies to all the Valkey data types made of multiple elements β Streams, Sets, Sorted Sets and Hashes.
Basically we can sum up the behavior in three rules:
- When we add an element to an aggregate data type, if the target key doesn't exist, an empty aggregate data type gets created before the element is added.
- When we remove elements from an aggregate data type, if the value is left empty, the key is automatically destroyed. The Stream data type is the only exception to this rule. π¨
- Calling a read-only command like
LLEN(which returns the length of the list), or a write command that removes elements, with an empty key, always gives the same result as if the key were holding an empty aggregate type of the type the command expects to find.
Examples of rule 1:
127.0.0.1:6379> DEL new_bikes
(integer) 0
127.0.0.1:6379> LPUSH new_bikes bike:1 bike:2 bike:3
(integer) 3
But we can't run operations against the wrong type if the key already exists:
127.0.0.1:6379> SET new_bikes bike:1
OK
127.0.0.1:6379> TYPE new_bikes
string
127.0.0.1:6379> LPUSH new_bikes bike:2 bike:3
(error) WRONGTYPE Operation against a key holding the wrong kind of value
Example of rule 2:
127.0.0.1:6379> RPUSH bikes:repairs bike:1 bike:2 bike:3
(integer) 3
127.0.0.1:6379> EXISTS bikes:repairs
(integer) 1
127.0.0.1:6379> LPOP bikes:repairs
"bike:3"
127.0.0.1:6379> LPOP bikes:repairs
"bike:2"
127.0.0.1:6379> LPOP bikes:repairs
"bike:1"
127.0.0.1:6379> EXISTS bikes:repairs
(integer) 0
The key no longer exists once all the elements are popped. Poof. Gone. π§
Example of rule 3:
127.0.0.1:6379> DEL bikes:repairs
(integer) 0
127.0.0.1:6379> LLEN bikes:repairs
(integer) 0
127.0.0.1:6379> LPOP bikes:repairs
(nil)
π Limits π
The max length of a List is 2^32 - 1 (4,294,967,295) elements. That's the ceiling, bestie. Don't test it. π
β‘ Performance β‘
List operations that access its head or tail are O(1), which means they're highly efficient β we love that for them. π
But commands that manipulate elements within a list are usually O(n). Examples include LINDEX, LINSERT, and LSET. So be careful running those, especially on big lists. Like, really careful. π
πΈ Alternatives πΈ
If you need to store and process an indeterminate series of events, consider valkey-streams-intro(7) Streams as an alternative to lists. Just putting that out there. π
That's the whole page, babes β and this beauty of a manual page is from Valkey version 9.0.0, dated 2025-10-21, filed under VALKEY-LISTS(7). Slay, go build something iconic with your lists. πβ¨
π¦ where this page comes from
- fedora 44: valkey-doc 9.0.3-2.fc44 Β· CC-BY-SA-4.0
- fedora 44: valkey-doc 9.0.6-1.fc44 Β· CC-BY-SA-4.0
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms