Knowledge Guide
HomeSystem DesignSystem Design Building Blocks

hard Bloom Filters: false positives & sizing

The mechanism: a shared bit array that only ever says “maybe” or “no”

A Bloom filter is an array of m bits, all starting at 0, checked through k independent hash functions. ADD(x) hashes x with each of the k functions to get k positions and sets every one of those bits to 1. CHECK(x) hashes x the same way and looks at the same k positions: if any bit is 0, x was definitely never added — that bit could only be 0 if nothing that hashed there ever set it. If all k bits are 1, the filter answers “probably present,” because those bits could have been set by x, or by pure coincidence from other items whose hashes happened to land on the same positions. That asymmetry is the entire contract: zero false negatives, some false positives — never the other way around.

Add / check, traced on a tiny filter

16-bit Bloom filter: ADD cat sets bits 2,5,9 and ADD dog sets bits 2,7,13; CHECK fox hashes to bits 5,9,13 which are all already set, producing a false positive; CHECK owl hashes to bits 1,4,11 where bit 1 is still 0, correctly reporting definite absence
16-bit Bloom filter: ADD cat sets bits 2,5,9 and ADD dog sets bits 2,7,13; CHECK fox hashes to bits 5,9,13 which are all already set, producing a false positive; CHECK owl hashes to bits 1,4,11 where bit 1 is still 0, correctly reporting definite absence

Why the error only ever goes one way

ADD only ever sets bits; CHECK only ever reads them; nothing ever clears a bit back to 0. So once x is added, its k bits are 1 forever, and a later CHECK(x) always finds all k of them — a Bloom filter can never produce a false negative. The only way CHECK can be wrong is the false-positive direction: some other combination of adds happened to set all k of a not-added item's bit positions by coincidence.

Sizing: turning “how wrong” into a dial

For n items inserted into an m-bit array with k hash functions, the probability that a random bit is still 0 after n inserts is approximately e−kn/m, so the probability all k of a queried item's bits are (coincidentally) 1 — the false-positive rate — is:

FP ≈ (1 − e^(−kn/m))^k

Two derived quantities matter more in practice than that raw formula: the bits you need for a target FP rate p, and the optimal number of hashes k for a given m and n:

m = −(n · ln p) / (ln 2)²        (required bits, for target false-positive rate p)
k = (m / n) · ln 2                (optimal hash count for that m, n)

The bits-per-item ratio (m/n) is independent of the item's actual size or content — sizing a filter for a billion 200-byte URLs costs exactly the same bits per item as a billion 8-byte integers. As a rule of thumb, m/n ≈ 1.44 · log₂(1/p), which gives ≈9.6 bits/item for a 1% target FP and ≈14.4 bits/item for 0.1%.

Traced example: 1,000,000 items, 1% target false-positive rate

  1. Inputs. n = 1,000,000 items, target p = 0.01 (1%).
  2. Bits. m = −(n · ln p) / (ln 2)² = −(1,000,000 · ln 0.01) / 0.4805 ≈ 9,585,058 bits (≈ 9.6 Mbit) — about 9.6 bits per item, matching the rule of thumb.
  3. Bytes. 9,585,058 / 8 ≈ 1,198,132 bytes ≈ 1.2 MB total, to hold a probabilistic membership test over a million items.
  4. Hash count. k = (m/n) · ln 2 = 9.585 · 0.6931 ≈ 6.64 → round to k ≈ 7 hash functions.
  5. Check the arithmetic. Plugging k=7, n=1,000,000, m=9,585,058 back into FP ≈ (1 − e−kn/m)k gives kn/m ≈ 0.730, e−0.730 ≈ 0.482, (1−0.482)71.00% — the design closes the loop on the 1% target.
Sizing trace for n=1,000,000 items and target false-positive rate 1 percent: step 1 inputs n and p, step 2 compute bits m equals 9,585,058 which is about 9.6 megabits, step 3 convert to bytes giving about 1.2 megabytes, step 4 compute optimal hash count k equals 6.64 rounded to 7, with a check confirming the resulting false-positive rate is about 1 percent
Sizing trace for n=1,000,000 items and target false-positive rate 1 percent: step 1 inputs n and p, step 2 compute bits m equals 9,585,058 which is about 9.6 megabits, step 3 convert to bytes giving about 1.2 megabytes, step 4 compute optimal hash count k equals 6.64 rounded to 7, with a check confirming the resulting false-positive rate is about 1 percent

Where this shows up in real systems

The shared shape: the Bloom filter is a cheap gate in front of an expensive lookup. It never gives a wrong answer by itself — a false positive just costs one extra fallback check; a false negative (which cannot happen) would have silently produced a wrong result, which is why the asymmetry is designed the way it is.

Pitfalls

Judgment: how a senior engineer decides

Bloom filter vs. an exact hash set

A hash set gives an exact answer with no false positives ever — but it must store (or hash-and-store) every actual key, so memory grows with the size of the keys, not just the count. A Bloom filter trades that exactness for roughly an order-of-magnitude-or-more memory reduction (≈1.2 MB vs. many times that for a million real keys), at the cost of an occasional, bounded, tunable false positive that only ever triggers an extra fallback check — never a wrong final answer. Use the Bloom filter when n is huge, the items themselves are non-trivial to store, and a false positive just costs one avoidable-but-cheap lookup. Use the exact set when you truly cannot afford any extra work on the fallback path, or when the data is small enough that the exact structure is cheap anyway.

Bloom filter vs. counting Bloom filter vs. cuckoo filter

The plain Bloom filter's fatal restriction is no deletes. A counting Bloom filter replaces each bit with a small counter (commonly 4 bits): ADD increments the k counters, DELETE decrements them, and CHECK still reads “all k counters > 0.” This supports deletion at roughly 4× the space of the plain filter, with a small added risk of counter overflow if too many items collide on one slot. A cuckoo filter takes a different approach: it stores a short fingerprint of each item in a cuckoo hash table (each item has two candidate buckets; on collision, an existing fingerprint is evicted and relocated to its alternate bucket). This supports exact deletion (remove the matching fingerprint) with better space efficiency than a counting Bloom filter at the same false-positive rate, at the cost of occasional insert-time relocations and a small chance of insert failure when the table is nearly full (fixed by growing the table or falling back to a stash). Choose the plain Bloom filter when the set is append-only or rebuilt wholesale (SSTables are exactly this — a new SSTable gets a fresh filter, an old one is dropped whole). Choose counting Bloom or cuckoo when individual items are added and removed continuously and you need the filter to track that in place.

When the space saving beats the false-positive cost

The decision is really a cost comparison: (space saved by using a Bloom filter) vs. (probability of FP × cost of the fallback lookup it triggers). Because a false positive only ever causes one extra real lookup — never a wrong answer returned to the caller — it is safe to accept a nonzero rate as long as that lookup is cheap relative to the savings. That is why Bloom filters are ubiquitous in front of disk and network I/O (where the fallback is a real but bounded cost) and rare in front of, say, an in-memory correctness check where doing the exact check is already nearly free.

Takeaways


Synthesized from Burton Bloom's original 1970 paper, Broder & Mitzenmacher's “Network Applications of Bloom Filters: A Survey,” the Cassandra/RocksDB SSTable Bloom-filter documentation, and Fan et al.'s Cuckoo Filter paper (2014). Re-authored/Deepened for this guide.

🤖 Don't fully get this? Learn it with Claude

Stuck on Bloom Filters: false positives & sizing? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.

🎨 Explain it visually

Build the mental picture, not memorization.

I just read a lesson on **Bloom Filters: false positives & sizing** (System Design) and want to truly understand it. Explain Bloom Filters: false positives & sizing from first principles using ONE vivid real-world analogy and a visual mental model — draw it as ASCII art or a clear step-by-step diagram — with a concrete example using real numbers. Then ask me one question to check I got the mental picture, and wait for my reply. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🤔 Walk me through it (interactive)

Socratic — adapts to where you're stuck.

Teach me **Bloom Filters: false positives & sizing** interactively. Ask me ONE guiding question at a time, wait for my answer, and adapt to my confusion — build the idea with me step by step instead of explaining it all at once. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧪 Quiz me & fix my gaps

Active recall exposes what you missed.

Quiz me on **Bloom Filters: false positives & sizing** with 5 questions, easy to tricky, ONE at a time. Tell me if each answer is right; at the end, explain clearly what I got wrong and why. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧠 Make it stick

Intuition + hook + flashcards for long-term memory.

Help me remember **Bloom Filters: false positives & sizing** for the long term: give the one-sentence intuition, a memorable hook/mnemonic, a tiny worked example, and 3 active-recall flashcards (Q -> A). If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.

📝 My notes