Data Structures & Algorithms (DSA) Module 3 – Hashing: maps, sets and hash functions
Bloom filters and count-min sketches
How a Bloom filter answers "definitely not" or "probably yes" in a few bits per key, how a count-min sketch estimates counts, and the errors they allow.
What you will learn
- Implement a Bloom filter and estimate its false-positive rate
- Explain why a Bloom filter never gives false negatives
- Estimate frequencies in a stream with a count-min sketch
- Size both structures from a target error and the number of items
Before you start
What you’ll learn · Before you start
What you will learn
- Implement a Bloom filter and estimate its false-positive rate
- Explain why a Bloom filter never gives false negatives
- Estimate frequencies in a stream with a count-min sketch
- Size both structures from a target error and the number of items
Before you start
On this page
Bloom filters answer “is this key in the set?” using a few bits per key, with an error that only goes one way: the answer is either “definitely absent” or “probably present”. Count-min sketches answer “how often has this item appeared?” from a small, fixed table of counters, and their estimates can be too high but never too low. Both give up exact answers to save memory, and both let you choose the error in advance: a Bloom filter with 10 bits per key is wrong for about 1 % of absent keys, whatever the keys look like. They are used where an exact set or exact counts would not fit in memory, or would cost a slow lookup: skipping disk reads for keys that are not there, or tracking the most frequent search terms in a stream too large to store.
How a Bloom filter works
A Bloom filter, described by Burton Bloom in 1970, is an array of m bits, all 0 at the start, and k hash functions. Adding a key sets the k bits that its hashes point to. Querying a key checks the same k bits: if any of them is 0, the key was never added; if all are 1, it probably was, because those bits may also have been set by other keys.
Adding and querying a Bloom filter
Text description of the diagram
The diagram is a flow from top to bottom.
- add(key) hashes the key k times and sets those k bits of the bit array.
- The example array of 16 bits, after adding asha, ravi and meera with k = 2, reads 0010010101101000.
- might_contain(key) hashes the key in the same k ways.
- A decision: are all k of those bits set?
- If not, the answer is "definitely absent": an added key always finds all its bits set, so there are no false negatives.
- If they are, the answer is "probably present": the bits may have been set by other keys, so this answer can be a false positive.
"""A Bloom filter of 16 bits with 2 hash functions, small enough to print every bit.
The two bit positions of a name come from 64-bit FNV-1a hashes of the name, combined as h1 + i * h2 (mod 16).
"""
M, K = 16, 2
def fnv1a_64(text, seed=0xCBF29CE484222325):
h = seed
for byte in text.encode("utf-8"):
h ^= byte
h = (h * 0x100000001B3) & 0xFFFFFFFFFFFFFFFF
return h
def positions(name):
h1, h2 = fnv1a_64(name), fnv1a_64(name, seed=0x84222325CBF29CE4) | 1
return [(h1 + i * h2) % M for i in range(K)]
bits = [0] * M
for name in ("asha", "ravi", "meera"):
for p in positions(name):
bits[p] = 1
print(f"add {name!r:8} -> bits {positions(name)}")
print("bit array:", "".join(map(str, bits)), f"({sum(bits)} of {M} bits set)")
for name in ("ravi", "john", "kiran", "farah", "neel", "tara"):
ps = positions(name)
if all(bits[p] for p in ps):
verdict = "probably present" + ("" if name in ("asha", "ravi", "meera") else " (a false positive)")
else:
verdict = "definitely absent"
print(f"query {name!r:8} -> bits {ps}: {verdict}") Output
add 'asha' -> bits [10, 9] add 'ravi' -> bits [5, 2] add 'meera' -> bits [7, 12] bit array: 0010010101101000 (6 of 16 bits set) query 'ravi' -> bits [5, 2]: probably present query 'john' -> bits [12, 13]: definitely absent query 'kiran' -> bits [10, 7]: probably present (a false positive) query 'farah' -> bits [15, 12]: definitely absent query 'neel' -> bits [5, 6]: definitely absent query 'tara' -> bits [7, 14]: definitely absent
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 tiny_bloom.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
After three names, 6 of the 16 bits were set. “john” hit bit 13, which is 0, so it is certainly absent. “kiran” was never added, yet both of its bits, 10 and 7, had been set by “asha” and “meera”: a false positive. False negatives are impossible, because adding a key sets all of its bits and nothing in a plain Bloom filter ever clears a bit. That is also why a plain filter cannot delete: clearing a key’s bits could clear bits that other keys share.
Two variants come up again below. A counting Bloom filter keeps a small counter instead of a bit in each cell, so that keys can be removed. Double hashing builds all k positions from two hashes, as h1 + i·h2, which Kirsch and Mitzenmacher showed keeps the same false-positive rate in the limit.
How a count-min sketch works
A count-min sketch, introduced by Cormode and Muthukrishnan in 2005, is a table of d rows of w counters, with one hash function per row. Adding an item adds 1 to one counter in every row, the one its row’s hash picks. The estimate for an item is the smallest of its d counters.
"""A count-min sketch of 2 rows by 8 counters, counting a short stream of search terms.
Each row hashes a term to one counter and adds 1 to it; the estimate of a term is the smallest of its counters, so it
can only be too high, never too low.
"""
from collections import Counter
D, W = 2, 8
def fnv1a_64(text, seed):
h = seed
for byte in text.encode("utf-8"):
h ^= byte
h = (h * 0x100000001B3) & 0xFFFFFFFFFFFFFFFF
return h
SEEDS = (0xCBF29CE484222325, 0x84222325CBF29CE4)
table = [[0] * W for _ in range(D)]
def add(term):
for row in range(D):
table[row][fnv1a_64(term, SEEDS[row]) % W] += 1
def estimate(term):
return min(table[row][fnv1a_64(term, SEEDS[row]) % W] for row in range(D))
stream = ["cricket", "rain", "cricket", "train", "rain", "cricket", "exam", "cricket", "gold", "rain", "train", "cricket"]
for term in stream:
add(term)
for row in range(D):
print(f"row {row}: {table[row]}")
exact = Counter(stream)
for term in sorted(exact, key=lambda t: (-exact[t], t)):
print(f"{term:>7}: exact {exact[term]}, estimate {estimate(term)}")
print(f"never seen 'chess': estimate {estimate('chess')}") Output
row 0: [5, 2, 1, 4, 0, 0, 0, 0] row 1: [0, 0, 4, 5, 0, 0, 2, 1] cricket: exact 5, estimate 5 rain: exact 3, estimate 4 train: exact 2, estimate 2 exam: exact 1, estimate 1 gold: exact 1, estimate 4 never seen 'chess': estimate 0
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 tiny_cms.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
Every counter an item touches includes all of that item’s own arrivals, so no estimate can be below the true count. Other items that land in the same counter only add to it, which is why “rain” was estimated at 4 instead of 3 and “gold” at 4 instead of 1. Taking the smallest counter keeps the error down, because an item would need to collide in every row to be badly overestimated. Count-min sketches estimate counts; they cannot list the items they have seen, because the table keeps no keys.
The formulas, and one worked number
For a Bloom filter with m bits, k hash functions and n keys, the chance that a key never added looks present is about
and it is smallest when k is about (m / n) ln 2. With 10 bits per key, the best k is 7 and p is about 0.82 %, so a filter for a million keys needs about 1.2 MB, against the tens of megabytes an exact set of the same keys would take.
For a count-min sketch over a stream of N items, choosing w = ⌈e / ε⌉ and d = ⌈ln(1 / δ)⌉ means that no estimate falls below the true count, and that each one exceeds it by more than εN with probability at most δ. With ε = 0.001 and δ = 0.01, that is 5 rows of 2,719 counters, whatever the number of distinct items.
Bloom filter, count-min sketch or a hash set?
| Criterion | Hash set or hash map | Bloom filter | Count-min sketch |
|---|---|---|---|
| Question it answers | Exactly: is it there, and how often | Is it there? Definitely not, or probably | About how often has it appeared? |
| Error | None | False positives only, at a rate you choose | Overestimates only, bounded by εN with probability 1 − δ |
| Memory | Grows with the keys, tens of bytes each | A few bits per key, about 10 for 1 % | Fixed: d × w counters, independent of the keys |
| Delete or decrement | Yes | No, unless counters replace bits | Decrements are possible but break the guarantee if counts go below the truth |
| Lists the keys | Yes | No | No |
| When to choose | The keys fit in memory and answers must be exact | Skipping work for keys that are certainly absent | Frequencies and heavy hitters in huge streams |
Storage engines show the trade-off well. LevelDB, an open-source storage library, can keep a Bloom filter for each of its files, and its documentation says that with 10 bits per key the filter cuts the unnecessary disk reads of a lookup by a factor of about 100. A read for a key that a filter rules out never touches that file.
Check yourself
Check yourself
4 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.
Bloom filters measured: false positives, sizing and deletion Pro: opens with a Pro, Premium or Ultimate pass
The formula for false positives is an approximation, so this part measures it on 100,000 keys, turns it into a sizing table you can use directly, and reproduces what goes wrong when a plain filter deletes.
In this part:
- False positives against the formula, measured for k = 1 to 10
- Sizing a filter from a target rate, with numbers
- Why a plain filter cannot delete, reproduced, and the counting fix
- Practise: build a Bloom filter
- Build a Bloom filter with k hash functions
- Results of the sample tests
3 runnable examples (Python) · 1 exercise with sample tests · about 4 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
Count-min sketches measured: error bounds and heavy hitters Pro: opens with a Pro, Premium or Ultimate pass
A count-min sketch promises a bounded overestimate with a stated probability. This part checks that promise on a skewed stream of 200,000 searches, proves where the bound comes from, and uses the sketch to find the most frequent terms without storing them all.
In this part:
- The error guarantee, checked on a skewed stream
- Why the overestimate is bounded: a proof sketch
- Finding the most frequent terms, with numbers
- Sizing a sketch for a whole day, with numbers
- When a count-min sketch is the wrong tool
1 runnable example (Python) · about 4 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
Interview questions
-
Fresher to mid · Warm-up
Why can a Bloom filter give false positives but never false negatives?
Adding a key sets all k bits that its hash functions point to, and a plain Bloom filter never clears a bit. So a key that was added always finds all its bits set, and the filter can never answer "absent" for it: no false negatives. A key that was never added can still find all its bits set, because other keys may have set them, and then the filter wrongly answers "probably present": a false positive. The rate of those depends on the number of bits per key and the number of hash functions, about 0.8 % with 10 bits per key and 7 hash functions. Deleting by clearing bits would break the guarantee, since other keys share those bits.
-
Mid to senior
A sign-up form must say at once whether a username might already be taken. There are 50 million existing usernames, and at most 1 % of "might be taken" answers may be wrong, each one checked against the database. Size a Bloom filter: bits, hash functions and memory. What happens as new users keep signing up?
-
Mid to senior
Your service keeps a Bloom filter of active account IDs to skip database lookups for unknown IDs. Accounts can now be deleted, about 1 % of them a month. How do you remove deleted IDs, and what are the trade-offs of each option?
-
Senior to staff
You must report the 100 most frequent search terms of a day with 2 billion searches, using a few megabytes of memory per server. Size a count-min sketch, say how far off an estimate can be, and explain how you find the top 100 without storing every term.
Key takeaways
- Bloom filters store set membership in a few bits per key and answer “definitely absent” or “probably present”.
- A plain Bloom filter has no false negatives and cannot delete; counting Bloom filters can, at about four times the memory.
- About 10 bits per key with 7 hash functions gives a false-positive rate near 0.82 %.
- Count-min sketches estimate frequencies from d rows of w counters; estimates are never too low, and too high by more than εN only with probability δ.
References
- Space/time trade-offs in hash coding with allowable errors (B. H. Bloom, Communications of the ACM, volume 13, number 7) (Association for Computing Machinery)
- An improved data stream summary: the count-min sketch and its applications (G. Cormode and S. Muthukrishnan, Journal of Algorithms, volume 55, number 1) (Elsevier)
- Less hashing, same performance: Building a better Bloom filter (A. Kirsch and M. Mitzenmacher, Random Structures & Algorithms, volume 33, number 2) (Wiley)
- Summary cache: a scalable wide-area Web cache sharing protocol (L. Fan, P. Cao, J. Almeida and A. Z. Broder, IEEE/ACM Transactions on Networking, volume 8, number 3) (IEEE)
- LevelDB documentation: filters (The LevelDB Authors)
- hashlib: BLAKE2 (Python Software Foundation)
- Dictionary of Algorithms and Data Structures: Bloom filter (National Institute of Standards and Technology (NIST))
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress