Bloom Filters: Saying 'Definitely Not' in a Few Kilobytes

A Bloom filter answers 'have I seen this before?' using a fraction of the memory a real set would need — at the cost of occasional false positives, never false negatives. A refresher on how it works, and why it quietly speeds up databases, caches, and AI training-data dedup.


Bloom filter diagram: inserting an element sets bits via several hash functions; a query finding a zero bit is a definite miss, all-ones is a probable hit

Find a zero among the hashed bits and the answer is a guaranteed “no.” All ones means “probably” — go check.

Sometimes the most useful answer a data structure can give you is a fast, cheap, confident “no.” Bloom filters do exactly that. They tell you whether an item is definitely not in a set, using a tiny slice of the memory that storing the set itself would cost. The price is a small, tunable chance of a false “yes.” That trade — a little uncertainty for a lot of memory savings — turns out to be one of the best deals in systems engineering, and it’s everywhere once you know to look.

The mechanism

A Bloom filter is just a big array of bits, all starting at zero, plus k independent hash functions.

To insert an item, run it through all k hash functions. Each produces a position in the bit array; set those k bits to 1. Inserting "cat" in the diagram flips three bits on.

To query an item, hash it the same way and check those k bits:

  • If any of them is 0, the item was never inserted — because inserting it would have set that bit. This is a guaranteed negative. "dog" hits a zero, so "dog" is definitely not in the set.
  • If all of them are 1, the item is probably present — but maybe those bits were set by other items that happened to collide. This is a maybe.

So a Bloom filter has a beautiful asymmetry: no false negatives, but some false positives. “Definitely not” is the truth. “Probably yes” needs confirmation.

Why this is such a good trade

The magic is the memory. A hash set storing a million URLs might cost tens of megabytes because it stores the actual strings. A Bloom filter storing the same million items, tuned for a 1% false-positive rate, costs about 1.2 MB — and lookups are a handful of bit checks. You give up the ability to retrieve or list what’s inside (a Bloom filter only answers membership, never “what’s in here?”) and you accept a controllable error rate. In exchange you get an order-of-magnitude-or-more memory reduction.

You tune the false-positive rate with two knobs: the size of the bit array (m) and the number of hash functions (k). More bits and a well-chosen k drive the error rate down. The one hard limitation of the classic version: you can’t delete items (unsetting a bit might break some other item’s membership). Variants like counting Bloom filters and cuckoo filters exist when you need deletes.

The pattern: use it as a cheap gate

The killer application is putting a Bloom filter in front of an expensive lookup:

Before you do the slow, costly thing — a disk read, a network call, a database query — ask the Bloom filter. If it says “definitely not,” skip the expensive thing entirely. If it says “maybe,” go do the real lookup.

Because most queries in many workloads are for things that aren’t there, the filter eliminates a huge fraction of pointless expensive work, and its false positives only cost you the occasional lookup you’d have done anyway.

Where it’s running right now

  • LSM-tree databases. This is the canonical use. Recall from the B-tree vs LSM-tree post that an LSM read might have to check many SSTables. Each SSTable has a Bloom filter; the engine asks “could this key be in here?” and skips every SSTable that says no. Cassandra, RocksDB, HBase, and Bigtable all lean on this to keep read amplification survivable.
  • CDNs and caches. “Is this object cached anywhere?” before an expensive origin fetch.
  • Web crawlers. “Have I already visited this URL?” across billions of URLs, in memory.
  • Databases avoiding disk. “Might this row exist?” before hitting storage.

The AI-era relevance

Training-data deduplication at scale. Deduplicating a web-scale training corpus — so the model doesn’t memorize the same document a thousand times — is a membership problem over trillions of items. Storing every hash exactly is enormous; a Bloom filter (or a scaled variant) makes “have I seen this shingle before?” cheap enough to run over the whole corpus. Dedup quality directly affects model quality, and Bloom filters are part of how it’s done affordably.

RAG ingestion and retrieval dedup. When you continuously ingest documents into a vector store, a Bloom filter over content hashes cheaply answers “have we already embedded this chunk?” — saving you from paying to re-embed and re-index duplicates. That’s real money when embedding APIs bill per token.

Rate limiting, safety, and “seen-before” checks. Has this agent already tried this exact action? Has this content already been flagged? Approximate, memory-cheap membership tests are a natural fit for the high-volume, low-latency guardrails around AI systems, where an exact set would be too big to keep hot.

The takeaway

Bloom filters are a reminder that exactness is a cost, and you can often trade a sliver of it for enormous efficiency. When the useful answer is a fast, cheap, certain “no,” and an occasional false “yes” only costs you a lookup you’d tolerate anyway, the probabilistic structure wins. That mindset — accept bounded, quantified error to buy scale — is exactly the mindset modern AI systems run on, which is why a data structure from 1970 keeps finding new jobs in the newest infrastructure we build.