Data Structures
Quotient Filter: Cache-Friendly Approximate Membership
Store a billion keys in a set, ask "have I seen this one?", and get an answer in a single cache miss using about 1.1 bytes per key at a 1% false-positive rate. That is the promise of the quotient filter, an approximate membership query (AMQ) data structure that answers set-membership questions with a small, tunable probability of false positives and zero false negatives.
Unlike a Bloom filter, which scatters each key across k random bit positions, a quotient filter stores a compact fingerprint for every key inside a single open-addressing hash table. Because all the bits for a lookup live in one contiguous run of slots, a query touches essentially one cache line — making it dramatically friendlier to modern memory hierarchies while remaining resizable, mergeable, and countable, things a classic Bloom filter cannot do.
- TypeApproximate membership query (AMQ) structure
- InventedBender et al., 2012 (quotienting: Cleary, 1984)
- Lookup / insertO(1) expected; O(cluster length) worst case
- Space~2.125 + log2(1/ε) bits per key
- Key ideaQuotient = slot index, remainder = stored fingerprint
- Used inSqueakr (counting QF, genomics), research LSM-tree forks, LSM-tree indexes
Interactive visualization
Press play, or step through manually. The visualization is yours to drive — try it before reading on.
Watch the 60-second explainer
A condensed visual walkthrough — narrated, captioned, under a minute.
The problem: membership without storing the keys
Many systems must answer one question millions of times per second: is this key in my set? A database wants to skip reading an SST file that cannot contain a row; a web cache wants to know if a URL was ever stored; a network router wants to test an IP against a blocklist. Keeping the full key set in memory is too expensive, so we accept a small lie: an approximate membership query structure may occasionally say "present" for an absent key (a false positive), but it will never say "absent" for a present key (no false negatives).
The quotient filter, introduced by Michael Bender and colleagues in 2012, solves this while fixing three chronic weaknesses of the Bloom filter:
- Cache behavior — a Bloom lookup probes k pseudo-random bits, causing up to k cache misses per query.
- Deletability — you cannot delete from a plain Bloom filter without corrupting other keys.
- Mergeability & resizing — two Bloom filters only merge if identical in size and hash count; a quotient filter can be resized and merged cheaply.
How it works: quotienting and Robin Hood clustering
Hash each key to a p-bit fingerprint f. Split it into two parts: the top q bits are the quotient and the bottom r bits are the remainder. The quotient is the canonical slot in a table of 2q slots; only the remainder is actually stored. This quotienting trick (Cleary, 1984) is lossless: slot index + stored remainder reconstruct the full fingerprint.
Collisions (two keys sharing a quotient) are resolved with linear probing plus Robin Hood hashing, which keeps remainders in sorted order within a run. Three metadata bits per slot track the structure:
is_occupied— some key canonically hashes to this slot.is_continuation— this slot continues a run (not the first remainder).is_shifted— this remainder was pushed out of its canonical slot.
insert(key):
f = hash(key)
q = f >> r; rem = f & ((1<<r)-1)
if slot[q] empty and not occupied:
slot[q]=rem; set is_occupied[q]; return
set is_occupied[q]
find run for quotient q (scan back to cluster start,
walk forward counting occupied slots)
insert rem in sorted order, shifting later
remainders right; set is_continuation/is_shiftedA run is all remainders sharing one quotient; a cluster is a maximal chain of runs with no empty slot between them.
Complexity and a worked step trace
Space: each slot holds r remainder bits plus 3 metadata bits. With r = log2(1/ε) chosen for target false-positive rate ε, the cost is about 2.125 + log2(1/ε) bits per key at load factor α ≈ 0.9 (the 3 metadata bits amortize to ~2.125 over the extra slot capacity). At ε = 1% that is roughly 9 bits/key — a hair better than a Bloom filter's 1.44·log2(1/ε) ≈ 9.6.
Time: insert, lookup, and delete are O(1) expected. The worst case is proportional to the length of the cluster you land in; for α < 1 the expected cluster length is O(1/(1−α)), and clusters stay short below α ≈ 0.75–0.9. The false-positive probability is ≈ 2−r (bounded by α·2−r).
r=3, table size 8. Insert keys with fingerprints:
A -> q=2 rem=5 : slot2=5, occ2=1
B -> q=2 rem=1 : run at 2 = [1,5] (sorted), slot2=1 slot3=5(cont,shift)
C -> q=3 rem=4 : occ3=1; slot3 taken by B, shift -> slot4=4(shift)
query(rem=1,q=2): scan run at 2 -> finds 1 => MAYBE present
query(rem=6,q=2): run at 2 = {1,5}, no 6 => DEFINITELY absent
Where it is used in real systems
Quotient filters and their descendants show up wherever Bloom filters strain under cache pressure or need deletion:
- LSM-tree databases — the rank-and-select quotient filter (RSQF) and the counting quotient filter (CQF, Pandey et al., 2017) back point-lookup filtering in write-heavy stores; RocksDB's ribbon/quotient-style filters and research forks use them to skip SST files with one cache miss.
- Bioinformatics — Squeakr and Mantis use the counting quotient filter to count and index k-mers across terabytes of sequencing data, exploiting the CQF's ability to store multiplicities, not just membership.
- Streaming / networking — deletable, resizable filters track active flows and sliding-window sets where a Bloom filter's inability to forget is disqualifying.
- Storage dedup and caches — the single-cache-line lookup and mergeability make quotient filters attractive for on-disk indexes that are periodically compacted and merged.
The CQF variant is notable for pairing membership with an approximate count using variable-length counter encodings inside the same runs.
Comparison to Bloom and cuckoo filters
All three are AMQ structures with no false negatives, but they trade off differently:
- vs. Bloom filter: the quotient filter uses one contiguous access instead of k scattered probes, supports deletion, resizing, and merging, and is marginally more space-efficient at practical ε. The Bloom filter is simpler and slightly faster to build when cache misses are cheap.
- vs. counting Bloom filter: counting Bloom filters gain deletion but at ~4× the space; the quotient filter deletes at no space penalty.
- vs. cuckoo filter: both store fingerprints and support deletion. Cuckoo filters use two candidate buckets (two cache lines) and can fail to insert near capacity, requiring a rebuild; quotient filters degrade gracefully as α → 1 but slow down. Cuckoo filters are often a touch smaller at low ε; quotient filters resize and merge more naturally.
The key structural distinction: Bloom spreads a key across the whole array; quotient and cuckoo filters localize a key's data, which is what unlocks deletion and cache-friendliness.
Pitfalls, failure modes, and significance
Load-factor cliff. Performance is excellent below α ≈ 0.75–0.9 but degrades sharply as slots fill: clusters coalesce, and a single insert may shift a long run. Sizing the table for expected load — and resizing before it saturates — is essential; unlike a hash map you cannot let it run to 100% full.
Metadata correctness. The three-bit invariant (is_occupied, is_continuation, is_shifted) is subtle: a buggy shift on insert or delete silently corrupts run boundaries and yields false negatives, breaking the core guarantee. Naive implementations also waste bandwidth reading metadata bit-by-bit — the RSQF fixes this with rank-and-select over packed metadata words.
- Fingerprint collisions set the false-positive floor at ≈ 2−r; you cannot beat it by adding slots, only by widening the remainder.
- Deletion must re-sort and un-shift the run, or remainders drift out of Robin Hood order.
- Not thread-safe by default; concurrent variants need locking or lock-free care.
Significance: the quotient filter reframed AMQ design around the memory hierarchy, and its descendants (RSQF, CQF) are now standard tooling in modern databases and genomics.
| Structure | Bits per key | Lookup cache misses | Delete / resize / merge | ||
|---|---|---|---|---|---|
| Bloom filter | 1.44 · log2(1/ε) (~9.6 at ε=1%) | k random probes | No / no / no | ||
| Counting Bloom filter | ~4× a Bloom filter | k random probes | Delete yes; resize no | ||
| Quotient filter | ~2.125 + log2(1/ε) (~9 at ε=1%) | ~1 (one cluster) | Yes / yes (resize) / yes | ||
| Cuckoo filter | ~3 + log2(1/ε) | 2 buckets | Delete yes; resize no | ||
| Rank-and-Select QF (RSQF) | ~2.125 + log2(1/ε), packed | ~1 | Yes / yes / yes (fast) |
Frequently asked questions
How is a quotient filter different from a Bloom filter?
A Bloom filter sets k pseudo-random bits per key across the whole array, so a lookup can incur k cache misses and deletion is impossible. A quotient filter stores each key's remainder in one open-addressing table using quotienting, so a lookup touches a single contiguous run (about one cache line) and supports deletion, resizing, and merging. Both guarantee no false negatives and a tunable false-positive rate.
What are the three metadata bits and why are they needed?
Each slot carries is_occupied (some key canonically hashes here), is_continuation (this slot extends a run rather than starting it), and is_shifted (this remainder was displaced from its home slot). Together they let a query reconstruct where each run begins and ends even after Robin Hood shifting, so you can find the run for a given quotient without storing the full quotient bits.
What is the false-positive rate and space cost?
With an r-bit remainder the false-positive probability is about 2^(-r), bounded by α·2^(-r) where α is the load factor. Total space is roughly 2.125 + log2(1/ε) bits per key at α ≈ 0.9. At ε = 1% that is about 9 bits/key, slightly better than a Bloom filter's ~9.6. Halving ε costs one extra remainder bit per key.
Why is it called 'cache-friendly'?
All of a key's stored data lives in one run of consecutive slots, and runs stay short at reasonable load factors, so a lookup almost always reads a single cache line. A Bloom filter, by contrast, probes k independent random positions, each likely a separate cache miss. On modern CPUs where memory latency dominates, one cache miss versus k is a large practical speedup.
What is the counting quotient filter (CQF)?
The CQF, by Pandey, Bender, Johnson, and Patro (2017), extends the quotient filter to store approximate counts, not just membership, by encoding variable-length counters inside the runs using a rank-and-select layout (RSQF). It is space-efficient and cache-efficient, and is used in genomics tools like Squeakr and Mantis to count k-mers across huge sequencing datasets.
When does a quotient filter perform badly?
As the load factor approaches 1, runs and clusters coalesce, so a single insert or lookup may scan and shift a long chain of slots, turning expected O(1) into a lengthy operation. Practical designs keep α below about 0.75–0.9 and resize before saturation. A buggy metadata update during insert or delete is worse: it can corrupt run boundaries and produce false negatives, violating the AMQ guarantee.