# HyperLogLog

> Count distinct items approximately in at most 12 KB per counter, and merge counters across periods.

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


A HyperLogLog (HLL) estimates how many **distinct** items you have added
to it, without storing the items themselves. Each counter takes at most
12 KB however many items it has seen, and its estimate is typically within
about 0.81% of the true count. Use it for unique visitors, distinct search
terms, unique IPs per hour, and any cardinality question where an exact
answer would mean keeping every item.

You cannot list or remove the items in a HyperLogLog, or ask whether one
specific item was added. For exact counts and membership tests use a
[set](https://docs.hotkv.com/data-types/sets/index.md); for approximate membership tests use a
[Bloom filter](https://docs.hotkv.com/data-types/probabilistic/index.md).

## Create, read, update, delete

[PFADD](https://docs.hotkv.com/commands/pfadd/index.md) adds items and creates the key if needed. It
replies `1` when the estimate changed and `0` when it did not (for example
when every item was already counted).
[PFCOUNT](https://docs.hotkv.com/commands/pfcount/index.md) returns the estimate.

```
127.0.0.1:6379> PFADD visitors:2026-10-02 "u:17" "u:23" "u:42" "u:88"
(integer) 1
127.0.0.1:6379> PFADD visitors:2026-10-02 "u:23"
(integer) 0
127.0.0.1:6379> PFCOUNT visitors:2026-10-02
(integer) 4
127.0.0.1:6379> PFADD visitors:2026-10-01 "u:42" "u:99"
(integer) 1
127.0.0.1:6379> PFCOUNT visitors:2026-10-01 visitors:2026-10-02
(integer) 5
127.0.0.1:6379> PFMERGE visitors:week visitors:2026-10-01 visitors:2026-10-02
OK
127.0.0.1:6379> PFCOUNT visitors:week
(integer) 5
127.0.0.1:6379> DEL visitors:week
(integer) 1
```

Counts this small are usually exact; as the number of distinct items grows,
the answer becomes an estimate.

- `PFCOUNT` with several keys returns the estimate for their union, without
  changing any of them.
- [PFMERGE](https://docs.hotkv.com/commands/pfmerge/index.md) stores the union in a destination key, so
  you can roll daily counters up into weekly or monthly ones.
- A HyperLogLog is a string, so `TYPE` reports `string` and
  [DEL](https://docs.hotkv.com/commands/del/index.md), [EXPIRE](https://docs.hotkv.com/commands/expire/index.md) and
  [RENAME](https://docs.hotkv.com/commands/rename/index.md) work as usual. Writing to it with string
  commands such as `SET` or `APPEND` replaces or corrupts it.

## Memory

A HyperLogLog starts out small and stays compact (sparse) until it would
take more than `hll-sparse-max-bytes` (3000 bytes by default); it then
switches to its full dense size of 12 KB, as in Redis. See
[Configuration](https://docs.hotkv.com/reference/configuration/index.md).

## Compatibility

HotKV stores HyperLogLogs in the same format as Redis, so their values can
move between Redis and HotKV (with `DUMP`/`RESTORE`, `MIGRATE`, or a plain
`GET` and `SET`), and both count the same items the same way.
[PFDEBUG](https://docs.hotkv.com/commands/pfdebug/index.md) and [PFSELFTEST](https://docs.hotkv.com/commands/pfselftest/index.md) are
there for diagnostics.

## Common patterns

**Unique visitors per day.** `PFADD uv:<date> <visitor-id>` on each page
view; `PFCOUNT uv:<date>` for the day, `PFCOUNT` over seven keys for the
week. Give each daily key an expiry so old days clean themselves up.

**Unique counts per dimension.** One counter per page, country, or
campaign (`uv:page:/pricing:<date>`): each costs at most 12 KB, so you can
afford many.

**Rollups.** Merge daily keys into a monthly key with `PFMERGE` at the end
of each day, then expire the daily keys.

## Expiry

A HyperLogLog expires as a whole, like any string.

## Commands

| Command | What it does |
| --- | --- |
| [PFADD](https://docs.hotkv.com/commands/pfadd/index.md) | Add items |
| [PFCOUNT](https://docs.hotkv.com/commands/pfcount/index.md) | Estimated number of distinct items (of one key, or the union of several) |
| [PFMERGE](https://docs.hotkv.com/commands/pfmerge/index.md) | Store the union of several HyperLogLogs |
| [PFDEBUG](https://docs.hotkv.com/commands/pfdebug/index.md) | Inspect a HyperLogLog's internal state |
| [PFSELFTEST](https://docs.hotkv.com/commands/pfselftest/index.md) | Run the built-in self test |

## From your application

**Python**

```python
import hotkv

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

client.pfadd("visitors:2026-10-02", "u:17", "u:23", "u:42")
client.pfadd("visitors:2026-10-01", "u:42", "u:99")
print(client.pfcount("visitors:2026-10-01", "visitors:2026-10-02"))  # 4
client.pfmerge("visitors:week", "visitors:2026-10-01", "visitors:2026-10-02")
client.close()
```

**Node.js**

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

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

await client.pfAdd("visitors:2026-10-02", "u:17", "u:23", "u:42");
await client.pfAdd("visitors:2026-10-01", "u:42", "u:99");
console.log(await client.pfCount("visitors:2026-10-01", "visitors:2026-10-02"));
await client.pfMerge("visitors:week", "visitors:2026-10-01", "visitors:2026-10-02");
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.PFAdd(ctx, "visitors:2026-10-02", "u:17", "u:23", "u:42")
client.PFAdd(ctx, "visitors:2026-10-01", "u:42", "u:99")
n, _ := client.PFCount(ctx, "visitors:2026-10-01", "visitors:2026-10-02").Result()
fmt.Println(n)
client.PFMerge(ctx, "visitors:week", "visitors:2026-10-01", "visitors:2026-10-02")
```

**Java**

```java
import com.hotkv.HotKV;

try (HotKV client = HotKV.create("hotkv://localhost:6379")) {
    client.pfAdd("visitors:2026-10-02", "u:17", "u:23", "u:42");
    client.pfAdd("visitors:2026-10-01", "u:42", "u:99");
    System.out.println(client.pfCount("visitors:2026-10-01", "visitors:2026-10-02"));
    client.pfMerge("visitors:week", "visitors:2026-10-01", "visitors:2026-10-02");
}
```
