# Bitmaps

> Treat a string as an array of bits for compact flags, daily-activity tracking, and packed integer counters.

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


Bitmaps are not a separate type: they are [strings](https://docs.hotkv.com/data-types/strings/index.md)
read and written one bit at a time. Bit offset 0 is the most significant
bit of the first byte. Because each flag costs a single bit, a bitmap can
record a yes/no fact for millions of numeric IDs in a few megabytes, and
the server can count or combine them quickly. Typical uses are "which users
were active today", feature flags per user ID, and presence grids.

[BITFIELD](https://docs.hotkv.com/commands/bitfield/index.md) goes one step further and packs small
integers (for example many 8-bit counters) into one string.

## Create, read, update, delete

[SETBIT](https://docs.hotkv.com/commands/setbit/index.md) sets or clears one bit and replies with its
previous value. A missing key is created, and the string grows (padded with
zero bits) to reach the offset. [GETBIT](https://docs.hotkv.com/commands/getbit/index.md) reads a bit;
bits past the end of the string read as 0.

```
127.0.0.1:6379> SETBIT logins:2026-10-01 3 1
(integer) 0
127.0.0.1:6379> SETBIT logins:2026-10-01 7 1
(integer) 0
127.0.0.1:6379> SETBIT logins:2026-10-02 3 1
(integer) 0
127.0.0.1:6379> SETBIT logins:2026-10-02 5 1
(integer) 0
127.0.0.1:6379> GETBIT logins:2026-10-02 5
(integer) 1
127.0.0.1:6379> GETBIT logins:2026-10-02 100
(integer) 0
127.0.0.1:6379> BITCOUNT logins:2026-10-01
(integer) 2
127.0.0.1:6379> BITPOS logins:2026-10-02 1
(integer) 3
```

Here the offset is a user ID: user 3 logged in on both days, user 7 only on
the first, user 5 only on the second.

- [BITCOUNT](https://docs.hotkv.com/commands/bitcount/index.md) counts set bits, optionally in a range.
  The range is in bytes by default; add `BIT` to give it in bits.
- [BITPOS](https://docs.hotkv.com/commands/bitpos/index.md) finds the first bit set to 1 (or 0),
  optionally within a range.
- Since a bitmap is a string, [GET](https://docs.hotkv.com/commands/get/index.md) returns the raw bytes
  and [DEL](https://docs.hotkv.com/commands/del/index.md) removes it.

## Combining bitmaps

[BITOP](https://docs.hotkv.com/commands/bitop/index.md) computes `AND`, `OR`, `XOR`, `NOT`, `DIFF`, `DIFF1`,
`ANDOR` or `ONE` across bitmaps and stores the result. It replies with the length of the result in
bytes.

```
127.0.0.1:6379> BITOP AND logins:both logins:2026-10-01 logins:2026-10-02
(integer) 1
127.0.0.1:6379> BITCOUNT logins:both
(integer) 1
127.0.0.1:6379> BITOP OR logins:either logins:2026-10-01 logins:2026-10-02
(integer) 1
127.0.0.1:6379> BITCOUNT logins:either
(integer) 3
```

`NOT` takes exactly one source key. Shorter inputs are treated as if padded
with zero bytes.

## Packed integers with BITFIELD

`BITFIELD` reads and writes integers of any width from 1 to 64 bits (signed
`i<n>`, or unsigned `u<n>` up to 63 bits) at any bit offset, several in one
call. `OVERFLOW` chooses what `INCRBY` does past the type's range: `WRAP`
(the default), `SAT` to stick at the minimum or maximum, or `FAIL` to leave
the value and return nil.

```
127.0.0.1:6379> BITFIELD player:42 SET u8 0 200
1) (integer) 0
127.0.0.1:6379> BITFIELD player:42 INCRBY u8 0 100
1) (integer) 44
127.0.0.1:6379> BITFIELD player:42 OVERFLOW SAT INCRBY u8 0 250
1) (integer) 255
127.0.0.1:6379> BITFIELD player:42 GET u8 0 GET u8 8
1) (integer) 255
2) (integer) 0
```

[BITFIELD_RO](https://docs.hotkv.com/commands/bitfield_ro/index.md) accepts only `GET`, so it can run on
read-only replicas.

## Common patterns

**Daily active users.** `SETBIT active:<date> <user-id> 1` on each visit.
`BITCOUNT` gives the day's count, `BITOP AND` across a week gives users
active every day, `BITOP OR` users active on any day.

**Per-user feature flags.** One bitmap per feature, offset = user ID.
`GETBIT feature:new-checkout 42` is a constant-time check.

**Many small counters.** With `BITFIELD`, a single key can hold thousands of
fixed-width counters, addressed by offset, with saturating increments.

## Limits and memory

A bitmap is as large as its highest set bit requires: setting bit
1,000,000 makes a string of about 122 KB even if no other bit is set. This
makes bitmaps a great fit for dense numeric IDs and a poor one for sparse
or very large IDs; for those, use a [set](https://docs.hotkv.com/data-types/sets/index.md), or a
[HyperLogLog](https://docs.hotkv.com/data-types/hyperloglog/index.md) if an approximate count is enough.

The highest usable bit offset follows `proto-max-bulk-len`: with the
default 512 MB, offsets go up to 2^32 - 1. Larger offsets are refused with
`ERR bit offset is not an integer or out of range`.

## Expiry

A bitmap expires as a whole, like any string. Name keys by period
(`active:2026-10-02`) and give each an expiry, so old periods clean
themselves up.

## Compatibility

Bitmap commands follow Redis 8.2, including the `DIFF`, `DIFF1`, `ANDOR` and
`ONE` operations of `BITOP`.

## Commands

| Command | What it does |
| --- | --- |
| [SETBIT](https://docs.hotkv.com/commands/setbit/index.md) | Set or clear a bit |
| [GETBIT](https://docs.hotkv.com/commands/getbit/index.md) | Read a bit |
| [BITCOUNT](https://docs.hotkv.com/commands/bitcount/index.md) | Count set bits |
| [BITPOS](https://docs.hotkv.com/commands/bitpos/index.md) | Find the first 0 or 1 bit |
| [BITOP](https://docs.hotkv.com/commands/bitop/index.md) | Combine bitmaps |
| [BITFIELD](https://docs.hotkv.com/commands/bitfield/index.md) | Read and write packed integers |
| [BITFIELD_RO](https://docs.hotkv.com/commands/bitfield_ro/index.md) | Read-only `BITFIELD` |

## From your application

**Python**

```python
import hotkv

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

client.setbit("logins:2026-10-02", 3, 1)
client.setbit("logins:2026-10-02", 5, 1)
print(client.getbit("logins:2026-10-02", 5))   # 1
print(client.bitcount("logins:2026-10-02"))    # 2
client.close()
```

**Node.js**

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

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

await client.setBit("logins:2026-10-02", 3, 1);
await client.setBit("logins:2026-10-02", 5, 1);
console.log(await client.getBit("logins:2026-10-02", 5));
console.log(await client.bitCount("logins: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.SetBit(ctx, "logins:2026-10-02", 3, 1)
client.SetBit(ctx, "logins:2026-10-02", 5, 1)
bit, _ := client.GetBit(ctx, "logins:2026-10-02", 5).Result()
count, _ := client.BitCount(ctx, "logins:2026-10-02", nil).Result()
fmt.Println(bit, count)
```

**Java**

```java
import com.hotkv.HotKV;

try (HotKV client = HotKV.create("hotkv://localhost:6379")) {
    client.setBit("logins:2026-10-02", 3, 1);
    client.setBit("logins:2026-10-02", 5, 1);
    System.out.println(client.getBit("logins:2026-10-02", 5));
    System.out.println(client.bitCount("logins:2026-10-02"));
}
```
