skip to content

Redis's BITFIELD command lets you treat a string as an array of arbitrary-width integers with u8, i5 or similar types. When is that worth using instead of ordinary keys, and what do the OVERFLOW WRAP, SAT and FAIL modes control?

level: seniorimportance: nice to knowfreq 20%

answer

  1. u1..u63 / i1..i64 fields, offset or #n indexing
  2. GET returns field, SET returns old value, INCRBY returns new
  3. OVERFLOW WRAP = modular, SAT = clamp, FAIL = nil + no write
  4. one atomic command, array of replies
  5. motivation = per-key overhead, cost = opacity

basics

~20 s

BITFIELD packs many small integers into one string, addressed by bit offset and width (u1..u63, i1..i64). It runs GET/SET/INCRBY operations atomically in one command. OVERFLOW chooses what happens on wrap: WRAP wraps around, SAT clamps at the limit, FAIL returns nil and skips the write.

solid answer

~60 s

`BITFIELD key <subcommand ...>` treats a string as a packed array of integers. Each operation names a type (`u` unsigned 1-63 bits, `i` signed 1-64 bits) and an offset — either an absolute bit offset or `#n`, meaning the n-th field of that width. Subcommands: `GET type offset`, `SET type offset value` (returns the old value), `INCRBY type offset delta`, and `OVERFLOW WRAP|SAT|FAIL`, which applies to every subsequent INCRBY/SET in the same command. All operations in one BITFIELD execute atomically and return an array of replies, so you can read three counters and bump a fourth in one round trip. Overflow modes: **WRAP** is modular arithmetic (the default), **SAT** clamps to the type's min/max, **FAIL** returns nil for that operation and leaves the value unchanged. Use it when you have many tiny per-entity counters — hourly buckets, small scores, capped retry counts — and one key per counter would cost far more in key overhead than the data itself. Do not use it as a general counter store: it is opaque, hard to debug, and offsets must be managed by your code.

code

text · 15 lines
text
# 24 hourly counters of 16 bits each, in one key per user
> BITFIELD u:42:hours OVERFLOW SAT INCRBY u16 #3 1 GET u16 #3
1) (integer) 1
2) (integer) 1

# saturation vs wrap on an 8-bit unsigned field at 255
> BITFIELD demo SET u8 #0 255
1) (integer) 0
> BITFIELD demo OVERFLOW WRAP INCRBY u8 #0 1
1) (integer) 0        # wrapped around
> BITFIELD demo SET u8 #0 255 OVERFLOW SAT INCRBY u8 #0 1
1) (integer) 0
2) (integer) 255      # clamped
> BITFIELD demo OVERFLOW FAIL INCRBY u8 #0 1
1) (nil)              # refused, value untouched

go deeper

for a junior

Know that BITFIELD packs small integers into a string and that the three overflow modes are wrap, saturate and fail.

for a middle

Explain the type/offset syntax including #n indexing, that SET returns the old value and INCRBY the new, and that the whole command is atomic.

for a senior

Justify the memory argument against per-key overhead, pick the overflow mode from the semantics of the counter, and name BITFIELD_RO for replica reads.

for a principal

Weigh the memory win against the operational cost of an undocumented binary layout, and set the rule for when a self-describing hash is the better default.

## What BITFIELD is Where SETBIT addresses single bits, BITFIELD addresses **fields**: contiguous runs of bits interpreted as integers. A type specifier is a letter plus a width — `u8` is an unsigned 8-bit field (0-255), `i16` a signed 16-bit field (−32768..32767). Unsigned widths go up to 63 bits, signed up to 64, because Redis's internal representation is a signed 64-bit integer. Offsets come in two forms. An absolute number is a bit position: `u8 16` is the byte at bits 16-23. The `#` form multiplies by the field width: `u8 #2` is the third 8-bit field, i.e. bits 16-23. The `#` form is what you use when the string is a homogeneous array; you never do the multiplication yourself. As with SETBIT, writing beyond the current end grows the string, zero-filling the gap — so the same density discipline applies. ## The subcommands and atomicity One BITFIELD call carries a list of operations, executed left to right, and returns an array with one reply per operation: - `GET type offset` — reads the field (missing bits read as 0). - `SET type offset value` — writes it, returning the **previous** value. - `INCRBY type offset delta` — adds (delta may be negative), returning the new value. - `OVERFLOW WRAP|SAT|FAIL` — not an operation itself; it sets the overflow behaviour for the SET/INCRBY operations that follow it in the same command. It may appear several times to change behaviour mid-command. The whole command is atomic, like every Redis command. That means "increment this counter and read those two others" is one indivisible step with one round trip — a genuine advantage over issuing several commands or a MULTI block. ## Overflow modes in detail Fixed-width fields overflow, and BITFIELD makes the behaviour explicit instead of leaving it to chance: - **WRAP** (default) — modular arithmetic. A `u8` at 255 incremented by 1 becomes 0; an `i8` at 127 incremented by 1 becomes −128. Correct for things that are genuinely cyclic (ring positions, rolling slot indexes), wrong and dangerous for counters, where a quota counter silently resets to zero at the worst moment. - **SAT** — saturating arithmetic. The value clamps at the type's maximum or minimum and stays there. This is usually what you want for capped counters: "at least 255" is a truthful, safe answer, and the value never lies about being small. - **FAIL** — the operation is refused: the reply element is nil and the stored value is untouched. Use this when overflow means the caller must take a decision (reject the request, escalate the type width) rather than silently accept a degraded number. GET operations are unaffected by OVERFLOW; only writes can overflow. ## When it earns its place The motivation is **per-key overhead**. Every Redis key carries dictionary entry, object header, expire-table entry and the key string itself — on the order of 50-100 bytes even when the value is a small integer. If you need 24 hourly counters per user for a million users, that is 24 million keys and gigabytes of overhead for a few hundred megabytes of actual numbers. Packing those 24 counters as `u16 #0` … `u16 #23` in one key per user reduces it to a million keys of 48 bytes of payload each, and lets you read the whole day in a single command. Good fits: dense homogeneous arrays of small integers — hourly/daily buckets, per-slot occupancy, small bounded scores, capped attempt counters, compact per-entity feature flags with more than two states. Poor fits: anything a human needs to read in redis-cli (a hash with named fields is enormously easier to operate), anything where the field layout will change (there is no schema, so a layout change means migrating raw bytes), or sparse fields, since the string is allocated up to the highest offset touched. ## Operational caveats - **The layout lives in your code.** Nothing in Redis records that bits 32-47 are "hour 2". Write the layout down and keep it in one place; a hash with field names is often worth the extra memory purely for maintainability. - **Choose the width for the real maximum.** Under-sizing forces you into WRAP or SAT behaviour that distorts data; over-sizing throws away the memory advantage that motivated BITFIELD. - **`BITFIELD_RO`** exists for read-only replicas and read-only script contexts: it accepts only GET, so it can be routed to replicas safely. - **Cost** is O(1) per subcommand, so a command with many operations costs proportionally to the number of operations — still one round trip, which is the point. In interviews, BITFIELD is a differentiator: knowing it exists, knowing the overflow modes, and — most importantly — knowing that its cost is readability, is the complete answer.

  • Why would you choose OVERFLOW SAT over the default WRAP for a quota counter?
    WRAP is modular arithmetic, so a u8 counter at 255 silently becomes 0 on the next increment — the quota resets exactly when it is most exceeded. SAT clamps at 255 and stays there, so the value never understates usage, and 'at the ceiling' remains detectable. FAIL is the third option when you want the caller to handle the overflow explicitly rather than accept a clamped number.
  • What do you give up by packing counters into a BITFIELD instead of using a hash with named fields?
    Readability and evolvability. Redis stores no schema, so the mapping from bit offsets to meanings exists only in your code, and inspecting the key in redis-cli shows opaque bytes. Changing the layout means rewriting raw values rather than adding a field. Hashes cost more memory but are self-describing, so BITFIELD is worth it only when the per-key or per-field overhead is genuinely the constraint.

saying these in an interview costs you the question

  • Assuming overflow always wraps and never checking the mode for counters that must not reset
  • Thinking each BITFIELD subcommand is a separate atomic step rather than the whole command being atomic
  • Using BITFIELD for a handful of counters where key overhead is irrelevant, just because it is clever
  • Believing Redis stores the field layout so the packing is self-describing
  • Expecting u64 to be a valid type (unsigned tops out at 63 bits)

context