When to use a probabilistic data structure
Use a Bloom filter when you need to:
- Ask "have I possibly seen this before?" for billions of items, in a few bits per item, and a wrong "yes" now and then is acceptable but a wrong "no" is not.
- Avoid an expensive lookup most of the time, such as a disk read or a database query for a key that does not exist.
Use a Count-Min sketch when you need to:
- Count how often each item appears in a huge or endless stream with fixed memory, and an estimate that is never too low is good enough.
- Find the heavy hitters, the few items that appear most often, such as trending hashtags or the busiest IP addresses.
Use something else when:
- You need exact answers or the set is small: use a hash set or hash map.
- You must delete items: a plain Bloom filter cannot (use a counting Bloom filter or a cuckoo filter).
- You need to count distinct items, not membership or frequency: use HyperLogLog.
- A wrong answer is costly and cannot be double-checked: use an exact structure.
Do not write your own in production. Use your database's or library's version (see the real-world section). Building a small one by hand is for learning and interviews.
How probabilistic data structures work
Exact structures store every item, so memory grows with the data. Probabilistic structures store a compact summary and accept a small, controlled error in return. They are always built from hashing: a hash function turns any item into a position, and several hash functions spread an item over several positions.
Think of a stamp card at a cafe where nobody writes names. Each visitor presses stamps on a few spots chosen by their name. If one of your spots is still blank, you were definitely never here. If all your spots are stamped, you were probably here, but other people's stamps may have covered them.
Teaching simplification: the code samples make
khash functions from one function plus a seed. Real libraries usually compute two good hashes and combine them (double hashing), which behaves much likekindependent hashes.
Bloom filter rule: a "no" is always correct, and a "yes" means "probably". It has false positives but never false negatives.
- A Bloom filter is a row of
mbits, all 0. To add an item, run it throughkhash functions and set thekbits they point to. To ask, check the samekbits: if any is 0, the item was definitely never added. If all are 1, it was probably added, because other items may have set those same bits. - A Count-Min sketch is a small table of counters with
drows andwcolumns, one hash function per row. To count an item, add 1 to one counter in each row. To estimate it, read itsdcounters and take the smallest. Other items sharing a counter can only add to it, so the estimate is never below the truth and the minimum is the least inflated.
Both can give a wrong answer, but in one known direction and with a probability you control by choosing the size.
A worked dry run
Take a 16-bit Bloom filter with k = 3 (the same hash used by the simulation). Add cat: its hashes point at bits 2, 10 and 13, so those become 1. Add dog: bits 4, 12 and 15 become 1. Now ask about zebra: its hashes point at bits 3, 10 and 12. Bits 10 and 12 are 1 (set by cat and dog), but bit 3 is 0, so the answer is definitely not. Ask about dog: bits 4, 12 and 15 are all 1, so the answer is probably yes, and here that is true. A false positive would need a never-added word whose three bits were all set by other words, which becomes likely as the filter fills.
Try it
Pick a structure and a size, then step through. Watch the false positives or the over-count.
Probabilistic Data Structures
Step 1 of 18 · Press Space to play, arrow keys to stepBit array (16 bits, 3 hash functions)
Initial state. A Bloom filter is 16 bits, all 0. To add a word, set 3 bits chosen by 3 hash functions. It stores no words at all.
- step:
- 0
- bits set:
- 0
- false positives:
- 0
Things to try:
- Run the Bloom filter with Roomy (64 bits, 3 words added). Words that were never added come back "definitely not". Then run Crowded (16 bits, 8 words added). More bits are set, so more never-added words are wrongly reported as "probably in the set".
- Run the Count-Min sketch with Roomy (32 counters per row). Estimates are exact or nearly so. Switch to Crowded (4 counters per row) and the total over-count rises, but no estimate is ever below the true count.
- In both, look at the counter row: the structure never stores the items themselves.
Probabilistic structure operations: add, query and size it
Bloom filter add. Compute k positions and set those bits to 1. Cost O(k).
Bloom filter query. Compute the same k positions. If any bit is 0, answer "definitely not". If all are 1, answer "probably yes". Cost O(k).
Count-Min add. For each of the d rows, add 1 to the counter at that row's hash position. Cost O(d).
Count-Min estimate. Read the counter in each row and return the minimum. Cost O(d).
Sizing a Bloom filter. For n items and a target false-positive rate p, the number of bits is about m = -n × ln(p) / (ln 2)², which is roughly 9.6 bits per item for 1% and 14.4 bits per item for 0.1%. The best number of hash functions is k = (m / n) × ln 2, about 7 for 1%. A bigger filter gives fewer false positives. With n items added, the false-positive rate is about (1 - e^(-kn/m))^k.
Sizing a Count-Min sketch. To keep the over-count within eps × N (where N is the total number of items seen) with probability at least 1 - delta, use width w = ceil(e / eps) and depth d = ceil(ln(1 / delta)). For example, eps = 0.001 and delta = 0.01 need 2,719 columns and 5 rows, which is 13,595 counters regardless of the stream length.
A Bloom filter has no false negatives, but it has false positives, and a Count-Min sketch never under-counts. This is the interview fact: both errors only go one way.
Edge cases. An empty Bloom filter answers "definitely not" to everything. A filter filled past its planned n has most bits set and answers "probably" to almost everything, so plan n in advance. Count-Min counters are fixed-size integers in real systems and can overflow on very long streams. An item never added gets an estimate of 0 only when at least one of its counters was never touched by other items.
Probabilistic structure time complexity
| Structure | Add | Query | Space | Error |
|---|---|---|---|---|
| Bloom filter | O(k) | O(k) | O(m) bits | False positives only |
| Counting Bloom filter | O(k) | O(k) | O(m) counters | False positives, supports delete |
| Count-Min sketch | O(d) | O(d) | O(d × w) counters | Over-counts only |
| HyperLogLog (distinct count) | O(1) | O(1) | A few kilobytes | About 1% error on the count |
| Hash set (exact) | O(1) average | O(1) average | O(n) items | None |
k is the number of hash functions, m the number of bits, d the number of rows and w the width of each. These costs do not depend on n, which is the point: the structure stays the same size as the stream grows.
Probabilistic structures vs exact structures
| Job | Bloom filter | Count-Min sketch | HyperLogLog | Hash set / map |
|---|---|---|---|---|
| "Is X in the set?" | Yes, with false positives | No | No | Exact |
| "How many times did X occur?" | No | Over-estimate | No | Exact |
| "How many different items?" | No | No | About 1% error | Exact but large |
| Memory for a billion items | About a gigabyte (1%) | Fixed, you choose | A few kilobytes | Tens of gigabytes |
| Can delete an item | No | Only by subtracting an item you know was added; otherwise estimates can be wrong | No | Yes |
| Answer can be wrong | Yes ("yes" only) | Yes (too high only) | Yes (slightly either way) | Never |
n is the number of items. These structures win when memory is the limit and a small, one-sided error is acceptable. They never replace an exact structure when every answer must be right.
How to explain probabilistic structures in an interview
- Say the trade first: "I trade a small, one-sided error for a large saving in memory." Then say which side: Bloom filters give false positives only, Count-Min sketches over-count only.
- Describe a Bloom filter in one breath: "a bit array and
khashes; setkbits to add; if allkbits are set, the answer is 'maybe'". Mention that you can compute the size from the false-positive rate you want. - Give a use that shows why it helps: "check a Bloom filter before reading the disk, so most misses never touch the disk."
- Name what they cannot do: delete (for plain Bloom), enumerate the items, or give exact counts. Offer the exact alternative when the data is small.