A Bloom filter trades certainty for space
Published
Why this, first. “A probabilistic set” earns nothing. Lead with the asymmetry: “definitely not here” is certain, “probably here” is not.
The picture: k hashes, one bit array
m-bit array. Insert sets k bits; a
query checks those k bits, and only one answer is certain.1 The asymmetry is the whole point
| Filter says… | What you actually know | Can it be wrong? |
|---|---|---|
| Not present | The item was never added | No — a negative is certain |
| Present | The item is probably there | Yes — a false positive |
A Bloom filter never lies about absence; it only ever exaggerates presence.A “no” lets you skip expensive work safely, every time.
Three operations — and the one that’s missing
| Operation | What it does | Wrong? |
|---|---|---|
add(x) | Set the k bits that x hashes to | — |
contains(x) | “no” if any bit is 0, else “probably” | FP only |
delete(x) | Not supported — bits are shared | n/a |
A counting Bloom filter swaps bits for small counters to allow deletes. 2
The one piece of math to carry in
Optimal k and the (0.5)k rate below are from the canonical survey. 2
(1−e−kn/m)k
(0.5)k
| Bits per item (m/n) | Optimal k | ≈ False-positive rate |
|---|---|---|
| 8 | 6 | ~2% |
| 10 | 7 | ~1% |
| 16 | 11 | ~0.05% |
That formula is an excellent approximation, not an exact law — worth saying out loud. 3 The headline: ~10 bits/item buys ~1% FP — a few bytes to skip a disk read, which is why LSM-tree stores like RocksDB and Cassandra use them. 4
Retrieval practice
Answer from memory — feedback is instant.
Q1. Which kind of error can a standard Bloom filter make?
Q2. Why can't a standard Bloom filter support delete?
Q3. Optimal hash count for m bits and n items is…
Q4. A query returns "not present." What is true?
Rehearse out loud
Interviewer: "Where would you put a Bloom filter in a read-heavy database, and what does it buy you?" ~60 seconds, from memory — then reveal and compare.
Put a small per-segment Bloom filter in front of the on-disk lookup — in an LSM-tree store like RocksDB or Cassandra, one per SSTable. On a read, check the filter first: a “not present” lets you skip that disk/SSD read entirely, and because there are no false negatives you never skip a key that’s actually there. It buys a large cut in read amplification for absent keys, at the cost of a few bits of RAM per key and the occasional wasted lookup on a false positive. I’d size it for ~1% FP (about 10 bits/key) and revisit if the filter saturates as segments grow — at which point I’d rebuild or partition rather than let the FP rate climb.
Why this scores: leads with the asymmetry (no false negatives → safe to skip), names a real system, and picks a sizing with a stated trade-off.
I’m your teacher — ask me anything. Want to go to Core next and size one for a target FP rate, be grilled on why the FP rate climbs as the filter fills, or have your rehearsal answer graded? Just say so.
Primary source (read this next)
📖
“Space/Time Trade-offs in Hash Coding with Allowable Errors” — Burton H. Bloom (1970)
1 . The original four-page paper; it frames the whole idea as trading a small, controllable error for a large space saving.
Interview — pick your bar
Answer out loud in ~60s, then reveal. Core = recall · Senior = trade-offs & failure modes · Staff = synthesis under ambiguity · System Design = open design round (a different axis, not a harder level).
What is a Bloom filter, in one sentence, and what does it store?
m-bit array → add(x) sets the k bits that x hashes to; contains(x) reads those k bits → the whole point is the asymmetry: it answers "definitely not here" or "probably here," never the reverse → you trade exactness for a tiny, controllable footprint.Which error can a standard Bloom filter make — and which can it never make?
Walk through what add and contains actually do at the bit level.
add(x): run k independent hashes of x, take each mod m, set those k bits to 1 (some may already be 1) → contains(x): hash the same way, read those k bits → if any is 0 → "definitely absent"; if all are 1 → "probably present" → no comparison against stored keys ever happens — there are none → cost is k hashes and k bit-probes, independent of how many items are in the set.Why can't a standard Bloom filter support delete?
x might un-set a bit that a still-present key y also relies on → that would introduce false negatives, which breaks the one guarantee the structure offers → so deletion is simply unsupported in the standard form → if you need deletes, reach for a counting Bloom filter (bits → small counters) or a cuckoo filter, or periodically rebuild from the live set.Where would you put a Bloom filter in a read-heavy database, and what does it buy you?
"Probably present" is useless — why bother if you still have to verify?
What is the optimal hash count, and what's the catch with using too many hashes?
k = (m/n)·ln 2, which minimizes the FP rate for a given array size → at that k the FP rate is about (0.5)k → intuition: too few hashes means too few independent bits checked; too many hashes fills the array faster, so each query trips over more set bits → there's a sweet spot — past it, adding hashes raises the FP rate and costs more CPU per op → also: real hashes must be well-mixed and effectively independent, or you fall short of the theoretical rate (double-hashing is the common cheap trick).The textbook FP formula — what is it, and why call it an approximation, not a law?
(1−e−kn/m)k → it assumes bit-set events are independent, which isn't strictly true — bits set by different items are correlated → so it's an excellent estimate, slightly optimistic, not an exact identity (NIST has a more precise analysis) → saying this out loud signals you understand the model's assumptions, not just the formula → practically it's accurate enough to size with, but don't treat the last decimal as gospel, and always add headroom for hash quality and growth.Make the case for a Bloom filter over just caching, then steelman the opposite.
A teammate proposes a Bloom filter as the source of truth for "has this user already claimed the coupon?" Where does this break?
When would you NOT use a Bloom filter at all?
- Clarify the workload: how often is the answer "absent"? Hits vs misses ratio decides if a filter helps at all.
- Pick the guarantee you need: a certain "no" (Bloom fits) vs an exact yes/no (needs a real index).
- Size it: choose target FP
p, countn, derivem = −(n·ln p)/(ln 2)2andk = log₂(1/p). - Place it: in front of the expensive/authoritative path; "maybe" always falls through to verify.
- Plan for growth: rebuild/shard/scalable variant before saturation pushes the FP rate up.
- Handle deletes/churn: counting or cuckoo variant, or periodic rebuild from the live set.
- Observe it: track measured FP rate, bits-set fraction, and verify-path hit rate, not just "it's there."
Design a "have we already crawled this URL?" check for a web crawler seeing billions of URLs.
n → n grows unboundedly, so use a scalable Bloom (chained layers) or shard filters by URL hash so the FP rate stays capped → no deletes needed (URLs don't un-crawl), so standard/scalable beats counting → instrument bits-set fraction and FP rate to trigger a new layer before saturation → name the trade-off: a false positive silently drops a page vs the memory of an exact set of billions of URLs.Design a "weak / breached password" rejection check at signup using a Bloom filter.
n of the breach corpus → the set is static and rebuilt on corpus updates, so no deletes and a simple rebuild pipeline → ship the filter to the edge/client for latency, since it stores no actual passwords → name the trade-off: occasional over-rejection (false positive) vs the privacy/size cost of querying or shipping the raw list.📄 Keep handy: Bloom filters reference
These notes reflect my current understanding and are updated as I learn, build, and discover better explanations.