Bloom filter: an interactive false positive simulator
A Bloom filter is a bit array that answers “have I seen this before?” with either “definitely not” or “probably yes”. Adding an item sets k bits chosen by k hash functions; checking it looks at the same k bits. It never says no to something it has seen, it sometimes says yes to something it has not, and at about 10 bits per item that false positive rate is 1%.
The playground is a 64-bit filter with three hash functions. Add some animals, then check colours you never added, and keep going until a colour comes back “probably yes”.
Add some animals, then check colours you never added. A Bloom filter never forgets an animal, and sometimes thinks it has seen a colour.
- Nothing yet.
How a Bloom filter works
- Start with m bits, all 0, and pick k hash functions that each map an item to a bit.
- To add an item, hash it k times and set those k bits to 1. A bit already at 1 stays 1.
- To check an item, hash it the same k times. If any of those bits is 0, the item was never added: definitely not.
- If all k bits are 1, the item was probably added, or other items happened to set every one of its bits: a false positive.
The filter stores no items, only bits, which is why it is so small and why it cannot list what it holds or remove anything: clearing a bit could clear it for another item too.
Bloom filter false positive rate
After n items, the chance that a given bit is still 0 is about e−kn/m, so the false positive rate is about (1 − e−kn/m)k. More precisely, it is the share of bits set, to the power k: an item never added has to land on k set bits by chance. The playground shows both the measured rate and the formula; on a small filter they differ by however much the actual words collided.
How big a Bloom filter should be
For n items and a target false positive rate p, use m = −n ln p / (ln 2)² bits and k = (m / n) ln 2 hash functions. Each halving of p costs a constant extra 1.44 bits per item.
| False positive rate | Bits per item | Hash functions | For 1 million items |
|---|---|---|---|
| 10% | 4.8 | 3 | 0.6 MB |
| 1% | 9.6 | 7 | 1.2 MB |
| 0.1% | 14.4 | 10 | 1.8 MB |
| 0.01% | 19.2 | 13 | 2.4 MB |
Compare a hash set of the same million items: storing even 40-byte URLs is 40 MB before any overhead. The Bloom filter is 30 times smaller at 1% and does not grow with the size of the items.
Bloom filter vs hash set
A hash set is exact and can list and delete what it holds, but it stores every item. A Bloom filter stores a few bits per item regardless of item size, and pays for that with false positives and no deletes. Use a Bloom filter in front of something expensive, where a false “probably yes” only costs an extra lookup and a “definitely not” saves one.
Where Bloom filters are used
| Where | The question it answers | What a “definitely not” saves |
|---|---|---|
| LSM-tree databases (RocksDB, Apache Cassandra, Google Bigtable) | Could this key be in this file on disk? | A disk read per file that does not have the key |
| Web crawlers | Have we queued this URL already? | A lookup in the big URL store, for billions of URLs |
| CDN caches (Akamai’s “one-hit wonders”) | Has this object been requested before? | Disk space: cache an object only on its second request |
| Sign-up forms | Is this username taken? | A database query for most names that are free |
To delete, use a counting Bloom filter (a small counter per position instead of a bit) or a cuckoo filter. To grow without knowing n in advance, chain filters of increasing size (a scalable Bloom filter).
A Bloom filter in code
import hashlib, math
class BloomFilter:
def __init__(self, n: int, p: float):
self.m = math.ceil(-n * math.log(p) / math.log(2) ** 2) # bits
self.k = max(1, round(self.m / n * math.log(2))) # hash functions
self.bits = bytearray((self.m + 7) // 8)
def _positions(self, item: str):
d = hashlib.sha256(item.encode()).digest()
h1 = int.from_bytes(d[:8], "big")
h2 = int.from_bytes(d[8:16], "big") | 1
return [(h1 + i * h2) % self.m for i in range(self.k)] # k hashes from two
def add(self, item: str) -> None:
for p in self._positions(item):
self.bits[p // 8] |= 1 << (p % 8)
def might_contain(self, item: str) -> bool:
return all(self.bits[p // 8] >> (p % 8) & 1 for p in self._positions(item))
seen = BloomFilter(n=1_000_000, p=0.01) # 9,585,059 bits (1.2 MB), k = 7Two good hashes are enough: the k positions come from h1 + i × h2, which is as good as k independent hash functions in practice. In Redis, the RedisBloom module gives you BF.ADD and BF.EXISTS without writing any of this.
In a system design interview
Reach for it when the question is “have we seen this?” at a scale where storing everything is expensive, and say the numbers: “A Bloom filter in front of the URL store: a billion URLs at 1% false positives is about 1.2 GB with 7 hash functions, so it fits in memory, and a false positive only costs one extra lookup.” Expect the follow-ups: deletes (counting filter), what a false positive costs the user, and how the filter is rebuilt or grown as the set grows.
Watch a strong candidate design a web crawler, or read the Bloom filters guide for the maths and the variants.
Questions people ask
What is a Bloom filter?
A compact bit array that tells you whether an item is definitely not in a set or probably in it. Adding an item sets k bits chosen by k hash functions; checking reads the same bits. It never misses an item it has seen, sometimes reports one it has not, and needs about 10 bits per item for a 1% false positive rate.
Can a Bloom filter have false negatives?
No. Adding an item sets all of its k bits, and bits are never cleared, so checking that item always finds them set. A Bloom filter can only be wrong in one direction: saying probably yes to an item it never saw.
How do you calculate the false positive rate of a Bloom filter?
After n items in m bits with k hash functions, the rate is about (1 − e^(−kn/m))^k. Put simply, it is the share of bits set raised to the power k, because an unseen item has to land on k set bits by chance. With 10 bits per item and 7 hash functions it is about 1%.
How big should a Bloom filter be?
Use m = −n ln p / (ln 2)² bits for n items and a false positive rate p, and k = (m / n) ln 2 hash functions. That is 9.6 bits per item and 7 hashes for 1%, or 14.4 bits and 10 hashes for 0.1%: a million items at 1% take 1.2 MB.
Why can’t you delete from a Bloom filter?
Each bit can be set by several items, so clearing an item’s bits could erase another item and create false negatives. A counting Bloom filter keeps a small counter per position instead of a bit, so deletes decrement it; a cuckoo filter also supports deletes.
Where are Bloom filters used?
In front of anything expensive to look up. LSM-tree databases such as RocksDB, Cassandra and Bigtable keep one per file on disk to skip files that cannot hold a key; web crawlers use them to skip URLs already queued; CDNs use them to cache an object only on its second request.
What is the difference between a Bloom filter and a hash set?
A hash set stores every item, so it is exact and can list and delete them, but its size grows with the items. A Bloom filter stores only a few bits per item whatever their size, in exchange for false positives and no deletes: a million 40-byte URLs is about 40 MB in a hash set and 1.2 MB in a 1% Bloom filter.