# Lists

> Ordered sequences of strings for queues, stacks, recent-item feeds, and blocking work distribution.

URL: https://docs.hotkv.com/data-types/lists/


A list is an ordered sequence of strings. You add and remove elements at
either end, read ranges by position, and can block until an element
arrives. Lists suit job queues, stacks, activity feeds ("the last 100
events"), and handing work from producers to consumers.

Elements keep the order you inserted them in, duplicates are allowed, and
positions start at 0 from the head (left). Negative positions count from the
tail: `-1` is the last element.

## Create, read, update, delete

There is no separate create step: pushing to a missing key creates the
list, and a list whose last element is removed is deleted.

```
127.0.0.1:6379> RPUSH tasks "resize:1" "resize:2" "resize:3"
(integer) 3
127.0.0.1:6379> LPUSH tasks "urgent:9"
(integer) 4
127.0.0.1:6379> LRANGE tasks 0 -1
1) "urgent:9"
2) "resize:1"
3) "resize:2"
4) "resize:3"
127.0.0.1:6379> LLEN tasks
(integer) 4
127.0.0.1:6379> LINDEX tasks -1
"resize:3"
127.0.0.1:6379> LSET tasks 1 "resize:1b"
OK
127.0.0.1:6379> LPOP tasks
"urgent:9"
127.0.0.1:6379> RPOP tasks 2
1) "resize:3"
2) "resize:2"
127.0.0.1:6379> LPOP tasks
"resize:1b"
127.0.0.1:6379> EXISTS tasks
(integer) 0
```

To change the middle of a list, use [LINSERT](https://docs.hotkv.com/commands/linsert/index.md) to add
before or after a given element, [LREM](https://docs.hotkv.com/commands/lrem/index.md) to remove
occurrences of a value, and [LPOS](https://docs.hotkv.com/commands/lpos/index.md) to find where a value
is.

```
127.0.0.1:6379> RPUSH letters a b c b
(integer) 4
127.0.0.1:6379> LPOS letters b
(integer) 1
127.0.0.1:6379> LPOS letters b COUNT 0
1) (integer) 1
2) (integer) 3
127.0.0.1:6379> LINSERT letters BEFORE c x
(integer) 5
127.0.0.1:6379> LREM letters 0 b
(integer) 2
127.0.0.1:6379> LRANGE letters 0 -1
1) "a"
2) "x"
3) "c"
```

[LPUSHX](https://docs.hotkv.com/commands/lpushx/index.md) and [RPUSHX](https://docs.hotkv.com/commands/rpushx/index.md) push only when
the list already exists.

## Common patterns

**Queue (first in, first out).** Producers `RPUSH`, consumers `LPOP`.
**Stack (last in, first out).** Push and pop at the same end.

**Capped feed.** Keep only the newest N items by trimming after each push:

```
127.0.0.1:6379> LPUSH feed:user:42 "liked post 7"
(integer) 1
127.0.0.1:6379> LTRIM feed:user:42 0 99
OK
```

**Reliable processing.** [LMOVE](https://docs.hotkv.com/commands/lmove/index.md) pops from one list and
pushes onto another atomically. Move a job from `jobs:pending` to
`jobs:processing`, and remove it from there with `LREM` once it is done.
If a worker dies, the job is still in `jobs:processing` for another worker
to pick up.

```
127.0.0.1:6379> RPUSH jobs:pending "job:1" "job:2"
(integer) 2
127.0.0.1:6379> LMOVE jobs:pending jobs:processing LEFT RIGHT
"job:1"
127.0.0.1:6379> LREM jobs:processing 1 "job:1"
(integer) 1
```

**Pop from several lists.** [LMPOP](https://docs.hotkv.com/commands/lmpop/index.md) pops from the first
non-empty list among the keys you name, which lets you poll queues in
priority order.

### Blocking consumers

Instead of polling, a consumer can wait for work. [BLPOP](https://docs.hotkv.com/commands/blpop/index.md)
and [BRPOP](https://docs.hotkv.com/commands/brpop/index.md) block until one of the lists has an element
or the timeout (in seconds, fractions allowed, `0` for no limit) passes.
The reply names the list the element came from.

```
127.0.0.1:6379> BLPOP jobs:high jobs:pending 5
1) "jobs:pending"
2) "job:2"
127.0.0.1:6379> BLPOP jobs:high jobs:pending 1
(nil)
```

The second call found both lists empty, waited one second, and timed out.

[BLMOVE](https://docs.hotkv.com/commands/blmove/index.md) and [BLMPOP](https://docs.hotkv.com/commands/blmpop/index.md) are the blocking
forms of `LMOVE` and `LMPOP`. See
[Blocking operations](https://docs.hotkv.com/develop/blocking-operations/index.md) for how waiting
clients are served.

## Expiry

A time to live applies to the whole list: set it with
[EXPIRE](https://docs.hotkv.com/commands/expire/index.md) and friends. Single elements cannot expire. If
you need per-item expiry, use a [sorted set](https://docs.hotkv.com/data-types/sorted-sets/index.md)
scored by timestamp and remove old members with `ZREMRANGEBYSCORE`, or a
[hash](https://docs.hotkv.com/data-types/hashes/index.md) with field TTLs.

Pushing to or popping from a list keeps its time to live. Removing the last
element deletes the key, and with it the expiry.

## Limits and performance

Each element is a string up to `proto-max-bulk-len` (512 MB by default).
Pushing and popping at either end take constant time. Commands that work by
position or by value (`LINDEX`, `LSET`, `LINSERT`, `LREM`, `LPOS`) walk the
list, so they get slower as the list grows; for large collections that you
look up by value, a [set](https://docs.hotkv.com/data-types/sets/index.md) or
[sorted set](https://docs.hotkv.com/data-types/sorted-sets/index.md) is the better fit. `LRANGE` costs
grow with the number of elements returned, so read big lists in pages.

## Commands

| Command | What it does |
| --- | --- |
| [LPUSH](https://docs.hotkv.com/commands/lpush/index.md) / [RPUSH](https://docs.hotkv.com/commands/rpush/index.md) | Add elements at the head or tail |
| [LPUSHX](https://docs.hotkv.com/commands/lpushx/index.md) / [RPUSHX](https://docs.hotkv.com/commands/rpushx/index.md) | Add only if the list exists |
| [LPOP](https://docs.hotkv.com/commands/lpop/index.md) / [RPOP](https://docs.hotkv.com/commands/rpop/index.md) | Remove and return elements from the head or tail |
| [LMPOP](https://docs.hotkv.com/commands/lmpop/index.md) | Pop from the first non-empty list |
| [LLEN](https://docs.hotkv.com/commands/llen/index.md) | Number of elements |
| [LRANGE](https://docs.hotkv.com/commands/lrange/index.md) | Elements in a range of positions |
| [LINDEX](https://docs.hotkv.com/commands/lindex/index.md) | Element at a position |
| [LSET](https://docs.hotkv.com/commands/lset/index.md) | Replace the element at a position |
| [LINSERT](https://docs.hotkv.com/commands/linsert/index.md) | Insert before or after an element |
| [LREM](https://docs.hotkv.com/commands/lrem/index.md) | Remove occurrences of a value |
| [LPOS](https://docs.hotkv.com/commands/lpos/index.md) | Positions of a value |
| [LTRIM](https://docs.hotkv.com/commands/ltrim/index.md) | Keep only a range of positions |
| [LMOVE](https://docs.hotkv.com/commands/lmove/index.md) | Move an element between lists |
| [RPOPLPUSH](https://docs.hotkv.com/commands/rpoplpush/index.md) | Older form of `LMOVE ... RIGHT LEFT` |
| [BLPOP](https://docs.hotkv.com/commands/blpop/index.md) / [BRPOP](https://docs.hotkv.com/commands/brpop/index.md) | Blocking pop |
| [BLMOVE](https://docs.hotkv.com/commands/blmove/index.md) / [BRPOPLPUSH](https://docs.hotkv.com/commands/brpoplpush/index.md) | Blocking move |
| [BLMPOP](https://docs.hotkv.com/commands/blmpop/index.md) | Blocking pop from several lists |

## From your application

**Python**

```python
import hotkv

client = hotkv.HotKV(host="localhost", port=6379, decode_responses=True)

client.rpush("jobs:pending", "job:1", "job:2")
job = client.lmove("jobs:pending", "jobs:processing", "LEFT", "RIGHT")
print(job)                                  # job:1
print(client.lrange("jobs:processing", 0, -1))

# Wait up to 5 seconds for work on either queue.
print(client.blpop(["jobs:high", "jobs:pending"], 5))
client.close()
```

**Node.js**

```ts
import { HotKV } from "@hotkv/client";

const client = new HotKV({ url: "hotkv://127.0.0.1:6379" });

await client.rPush("jobs:pending", "job:1", "job:2");
const job = await client.lMove("jobs:pending", "jobs:processing", "LEFT", "RIGHT");
console.log(job);
console.log(await client.lRange("jobs:processing", 0, -1));

// Wait up to 5 seconds for work on either queue.
console.log(await client.blPop(["jobs:high", "jobs:pending"], 5));
await client.close();
```

**Go**

```go
client, err := hotkv.NewClient(&hotkv.Options{Addr: "localhost:6379"})
if err != nil {
    log.Fatal(err)
}
defer client.Close()
ctx := context.Background()

client.RPush(ctx, "jobs:pending", "job:1", "job:2")
job, _ := client.LPop(ctx, "jobs:pending").Result()
fmt.Println(job)

items, _ := client.LRange(ctx, "jobs:pending", 0, -1).Result()
fmt.Println(items)

// Wait up to 5 seconds for work on either queue.
res, err := client.BLPop(ctx, 5*time.Second, "jobs:high", "jobs:pending").Result()
fmt.Println(res, err)
```

**Java**

```java
import com.hotkv.HotKV;

try (HotKV client = HotKV.create("hotkv://localhost:6379")) {
    client.rpush("jobs:pending", "job:1", "job:2");
    String job = client.lmove("jobs:pending", "jobs:processing", "LEFT", "RIGHT");
    System.out.println(job);
    System.out.println(client.lrange("jobs:processing", 0, -1));
}
```
