Chapter 03 · Strings, Counters, Bitmaps, Bitfields, and Binary Values

Bitmaps with SETBIT/GETBIT/BITCOUNT/BITOP for Dense Boolean State

Model dense boolean state as bits inside Redis strings, prove bit ordering and counting, combine cohorts with BITOP, and expose the memory and latency danger of far sparse offsets.

Intermediate115–145 minutesBitmap analytics labRedis Open Source 8.10.1Free/local-firstLast reviewed: September 6, 2026

Learning outcomes

AtlasMart wants to record whether each customer was active on a day, whether a product was included in a campaign, and which users belong to overlapping cohorts. Storing one key per boolean would create enormous key cardinality. A Redis bitmap is not a separate data type; it is a set of bit-oriented operations over a String. That makes dense boolean state extremely compact—but it also means a far bit offset grows a real String.

01

Explain bitmap storage as bits inside a Redis String, including zero-based offsets, bit ordering, implicit zero extension, and the 512-MiB bitmap limit.

02

Use SETBIT/GETBIT to mutate/read dense boolean state and interpret the returned previous bit correctly.

03

Use BITCOUNT with byte/bit range awareness and BITOP to combine bounded cohort bitmaps while accounting for O(N) work and destination writes.

04

Measure STRLEN and MEMORY USAGE before/after dense and sparse writes, and connect far offsets to allocation latency and tail-latency risk.

05

Design IDs/offsets, rotation, Cluster locality, persistence/replication cost, ACLs, and observability so bitmap efficiency does not become a hidden big-key problem.

Exact lab baseline

All Chapter 03 labs reuse the disposable Chapter 01 environment: Redis Open Source 8.10.1 from Docker Official Image redis:8.10.1, container atlasmart-redis-ch01, standalone topology, host publication 127.0.0.1:6379, TLS disabled only because traffic stays on loopback, default user disabled, named ACL users atlasmart-app and academy-admin, logical database 0, AOF with appendfsync everysec plus RDB snapshots, persistent /data volume, and no explicit Redis maxmemory limit/eviction policy. The application ACL is restricted to ~atlasmart:* and normal read/write/connection categories. Search, JSON, vector, time-series, and probabilistic features are not required. Bitmap fixtures use small synthetic customer IDs. A bounded far-offset demonstration allocates about 1 MiB, then immediately deletes that key.

1. A bitmap is a String viewed as a sequence of bits

SETBIT and GETBIT address bit offsets within String bytes. Offset 0 is the most significant bit of the first byte. When SETBIT reaches beyond the current String length, Redis grows the String and initializes intermediate bits to zero. Current Redis documentation requires offsets below 2^32, which caps a bitmap at 512 MiB.

redis-cli · bit ordering becomes observable as a byte
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app UNLINK atlasmart:ch03:bitmap:bytedocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app SETBIT atlasmart:ch03:bitmap:byte 0 1docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app GETBIT atlasmart:ch03:bitmap:byte 0docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app STRLEN atlasmart:ch03:bitmap:bytedocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app BITCOUNT atlasmart:ch03:bitmap:byte# SETBIT returns the previous bit (0 here).# One bit at offset 0 creates a one-byte String and BITCOUNT reports 1.

Because terminals are poor tools for inspecting non-printable bytes, use bit commands or a byte-aware client rather than interpreting GET output visually. Lesson 3's binary RESP script can also inspect the underlying byte.

2. SETBIT/GETBIT make dense boolean IDs cheap

If AtlasMart assigns a compact integer customer index, bit N can represent one boolean attribute for customer N. The payload cost is roughly (max_offset + 8) / 8 bytes, rounded to whole bytes, regardless of how many bits are set. That is excellent when IDs are dense; it is wasteful when one rare high ID forces a huge mostly-zero String.

redis-cli · build one tiny activity cohort
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app UNLINK atlasmart:ch03:bitmap:activedocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app SETBIT atlasmart:ch03:bitmap:active 1 1docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app SETBIT atlasmart:ch03:bitmap:active 3 1docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app SETBIT atlasmart:ch03:bitmap:active 7 1docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app GETBIT atlasmart:ch03:bitmap:active 3docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app GETBIT atlasmart:ch03:bitmap:active 4docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app BITCOUNT atlasmart:ch03:bitmap:activedocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app STRLEN atlasmart:ch03:bitmap:active# GETBIT 3 -> 1; GETBIT 4 -> 0; count -> 3; length -> 1 byte.

SETBIT returns the previous bit, so it can tell the caller whether a membership flag actually changed. That is still only one-bit state. If you need counts, timestamps, or rich membership metadata, another structure may fit better.

3. BITCOUNT is population count, with range semantics

BITCOUNT counts set bits. Without a range it scans the whole String, so its complexity is O(N) in String length. Current Redis supports ranges with byte units and, when requested, bit units. Large bitmaps therefore need bounded query design rather than assuming every count is O(1).

redis-cli · count a whole bitmap and a byte range
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app BITCOUNT atlasmart:ch03:bitmap:activedocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app BITCOUNT atlasmart:ch03:bitmap:active 0 0 BYTE# Both are 3 for this one-byte fixture.

For dashboards that count large historical bitmaps continuously, pre-aggregation, rotation by time bucket, or off-primary computation may be safer. Measure command duration and tail latency on realistic bitmap lengths.

4. BITOP combines cohorts and writes another String

BITOP performs bitwise operations over source Strings and stores the result in a destination key. Classic operations are AND, OR, XOR, and unary NOT. Current Redis 8.10 documentation also exposes additional set-like bit operations such as DIFF/DIFF1/ANDOR/ONE; these are version-sensitive extensions, so check target-version support before using them in portable application code.

redis-cli · combine two synthetic cohorts
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app UNLINK atlasmart:ch03:bitmap:buyers atlasmart:ch03:bitmap:mobile atlasmart:ch03:bitmap:targetdocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app SETBIT atlasmart:ch03:bitmap:buyers 1 1docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app SETBIT atlasmart:ch03:bitmap:buyers 3 1docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app SETBIT atlasmart:ch03:bitmap:buyers 5 1docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app SETBIT atlasmart:ch03:bitmap:mobile 3 1docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app SETBIT atlasmart:ch03:bitmap:mobile 4 1docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app SETBIT atlasmart:ch03:bitmap:mobile 5 1docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app BITOP AND atlasmart:ch03:bitmap:target atlasmart:ch03:bitmap:buyers atlasmart:ch03:bitmap:mobiledocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app BITCOUNT atlasmart:ch03:bitmap:targetdocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app GETBIT atlasmart:ch03:bitmap:target 3docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app GETBIT atlasmart:ch03:bitmap:target 5# Intersection count -> 2; bits 3 and 5 are set.

BITOP is O(N) in the processed input length and writes a destination String. Source Strings of different lengths are effectively zero-padded to the longest relevant length. In Cluster, multi-key BITOP requires slot-local design; a destination plus sources cannot be treated as an arbitrary cross-shard operation.

5. Dense versus sparse is the central bitmap design decision

A bitmap with maximum offset 7 needs one payload byte. A bitmap whose only set bit is offset 8,388,607 needs 1,048,576 bytes of String payload. The number of true values is irrelevant to that allocation. This is why “one bit per external customer ID” is dangerous if IDs are UUID-derived or sparse.

Offset pattern Approximate payload shape Good fit?
0..999,999 dense ~125 KiB Often excellent for one boolean per compact integer ID.
One bit at 8,388,607 1 MiB mostly zero Potentially wasteful; demonstrates sparse-growth risk.
Random 64-bit customer IDs Astronomical impossible offsets Not a bitmap indexing scheme; remap IDs or use another structure.
Daily bounded ID range Separate rotated bitmap per day Often easier retention and bounded BITCOUNT/BITOP cost.

6. Deliberately wrong approach: write a far sparse bit because SETBIT is O(1)

The command's per-bit lookup is O(1), but first growth must allocate intermediate memory. The official SETBIT documentation explicitly warns that setting the highest possible bit on a small/absent String can block while Redis allocates hundreds of MiB. We will not execute that dangerous maximum. Instead, set the last bit of a 1-MiB String and measure the jump.

redis-cli · bounded sparse-bit allocation
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app UNLINK atlasmart:ch03:wrong:bitmap-sparsedocker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user academy-admin MEMORY USAGE atlasmart:ch03:wrong:bitmap-sparsedocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app SETBIT atlasmart:ch03:wrong:bitmap-sparse 8388607 1docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app STRLEN atlasmart:ch03:wrong:bitmap-sparsedocker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user academy-admin MEMORY USAGE atlasmart:ch03:wrong:bitmap-sparsedocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app BITCOUNT atlasmart:ch03:wrong:bitmap-sparse# STRLEN -> 1,048,576 bytes; BITCOUNT -> 1.# Exact MEMORY USAGE depends on allocator/build.docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app UNLINK atlasmart:ch03:wrong:bitmap-sparse

The repair is an ID mapping or structure choice that makes density explicit. For example, map stable customer identifiers to bounded numeric indexes in authoritative metadata, rotate bitmaps by period, or use Sets when membership is sparse and individual identifiers matter more than dense bitwise analytics.

7. Hands-on lab: AtlasMart campaign cohorts

Create two 64-customer synthetic cohorts under one-byte-to-eight-byte scale, compute intersection and union, and inspect counts/memory. This stays tiny so the result is deterministic and does not pretend to benchmark production.

shell · cohort intersection and union
docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app UNLINK atlasmart:ch03:l4:seen atlasmart:ch03:l4:buyers atlasmart:ch03:l4:both atlasmart:ch03:l4:eitherdocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app SETBIT atlasmart:ch03:l4:seen 2 1docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app SETBIT atlasmart:ch03:l4:seen 3 1docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app SETBIT atlasmart:ch03:l4:seen 9 1docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app SETBIT atlasmart:ch03:l4:seen 31 1docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app SETBIT atlasmart:ch03:l4:buyers 3 1docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app SETBIT atlasmart:ch03:l4:buyers 9 1docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app SETBIT atlasmart:ch03:l4:buyers 12 1docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app BITOP AND atlasmart:ch03:l4:both atlasmart:ch03:l4:seen atlasmart:ch03:l4:buyersdocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app BITOP OR atlasmart:ch03:l4:either atlasmart:ch03:l4:seen atlasmart:ch03:l4:buyersdocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app BITCOUNT atlasmart:ch03:l4:bothdocker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app BITCOUNT atlasmart:ch03:l4:eitherdocker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user academy-admin MEMORY USAGE atlasmart:ch03:l4:seendocker exec -e REDISCLI_AUTH=AtlasMart-Admin-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user academy-admin MEMORY USAGE atlasmart:ch03:l4:either# Intersection count -> 2 (IDs 3,9). Union count -> 5 (2,3,9,12,31).# Cleanup:docker exec -e REDISCLI_AUTH=AtlasMart-App-Lab-Only-2026 atlasmart-redis-ch01 redis-cli --user atlasmart-app UNLINK atlasmart:ch03:l4:seen atlasmart:ch03:l4:buyers atlasmart:ch03:l4:both atlasmart:ch03:l4:either atlasmart:ch03:bitmap:byte atlasmart:ch03:bitmap:active atlasmart:ch03:bitmap:buyers atlasmart:ch03:bitmap:mobile atlasmart:ch03:bitmap:target

Verification checklist:

  • You can explain why a bitmap key still reports Redis type string.
  • You can map a bit offset to byte growth and explain why sparse high IDs waste memory.
  • You can distinguish SETBIT return value (previous bit) from GETBIT current value and BITCOUNT population count.
  • You can explain BITOP destination writes, O(N) cost, and multi-key Cluster locality requirements.
  • You measured STRLEN/MEMORY USAGE and avoided the dangerous maximum-offset allocation demonstration.

8. Production judgment

Bitmaps fit high-density boolean dimensions with a stable integer index and operations such as population count and set intersection. They are a poor default for sparse external identifiers, rich per-member metadata, or independent member TTLs. Bound the maximum offset, bitmap length, rotation window, number of concurrent BITOP results, and destination retention. Watch command latency, allocator/RSS, big-key sizes, AOF/replication bytes, fork/copy-on-write headroom, and Cluster slot skew.

A hot bitmap is still one key and one shard. Parallel writers to different bits do not distribute the key across shards. If analytics BITOP scans large Strings, consider where and when it runs, and verify freshness if using replicas later. ACLs should restrict bitmap keys to intended applications; the fact that bits are compact does not make their membership data non-sensitive.

9. Summary and next step

Redis bitmaps are bit operations on Strings. SETBIT/GETBIT address zero-based bits, BITCOUNT scans set-bit populations, and BITOP writes combined bitmaps. Dense offsets can compress millions of booleans efficiently; sparse far offsets allocate all intermediate bytes and can harm memory and tail latency. Next, BITFIELD generalizes this idea from one-bit booleans to packed signed and unsigned integers with explicit widths, offsets, and overflow behavior.

Check your understanding

  1. Why does TYPE return string for a bitmap?
  2. What does SETBIT return?
  3. Why can one true bit consume 1 MiB?
  4. What is the complexity concern with BITCOUNT/BITOP on large bitmaps?
  5. What key-design issue appears in Cluster?
Review the answers

1. Bitmap commands operate on the String data type; bitmap is a view/operation family, not a separate Redis type.

2. The previous bit value at that offset.

3. If its offset is 8,388,607, the String must grow to one MiB and zero-fill all earlier bits.

4. They process bytes across the String, so work grows with input length rather than the number of set bits.

5. BITOP is multi-key and all participating keys/destination must satisfy the target Cluster locality rules.

Authoritative references

Keep knowledge open

Help the academy stay free and grow.

If these tutorials save you time, a small donation supports new lessons, technical review, diagrams, examples, and long-term maintenance.

ETHEthereum / ERC-20 only
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0

Send only Ethereum or ERC-20 compatible assets to this address.