Bloom Filters and Count-Min Sketch
Prerequisite Knowledge
This lecture builds on the following concepts from earlier lectures. If any feel unfamiliar, review the linked notes before proceeding.
Previously Covered in This Subject
- Bloom filter (preview) - Lecture 13 previews the filter and covers the membership and frequency operations in streaming.
- Streaming algorithms - Lecture 12 surveys the families of streaming algorithms, random and reservoir sampling, and why stream algorithms are non-deterministic.
- Probabilistic streaming algorithms - Lecture 1 introduces deterministic versus probabilistic streaming algorithms.
- Filtering and aggregation - Lecture 2 frames filtering and aggregation as the two core stream computations.
Two algorithms sit at the heart of windowed stream analytics. A Bloom filter answers one question about an element: is it a member of the current window or not? A count-min sketch answers a different one: how many times has it appeared? Both trade a small amount of error for a huge saving in memory, and both matter for the exam. This session works through both from first principles — the bit-sequence idea behind the Bloom filter, the collision problem, the false positive analysis with its full derivation, a worked problem in the style of an exam question, and then the row-and-column mechanics of the count-min sketch.
The stakes are practical. A window can hold on the order of 10^6 to 10^7 events, and keeping an exact set or an exact per-key count at that scale costs memory that streaming servers cannot spare. The two structures in this session answer the two questions stream analytics actually asks — "has this arrived before?" and "how often does this arrive?" — with a few million bits and a small table of counters, in exchange for an error you can size and control. By the end you should be able to compute that error by hand, which is exactly what the exam asks for.
Roadmap. Section 14.1 builds the Bloom filter: the bit-sequence idea, hashing, collisions, the multi-hash design, the false positive formula and its derivation, the optimal number of hash functions, and a four-part worked problem. Section 14.2 builds the count-min sketch: the counter table, the insertion rule, and the minimum-at-query-time rule. Two appendices close with exam guidance and industry applications.
14.1 Bloom Filter
14.1.1 The Core Idea: A Bit Sequence Instead of a Boolean Array
Hook: How can a filter hold the membership of a million-event window in a few million bits — without storing any of the events themselves? The first step is to stop using one Boolean per element, and instead use one bit per element.
A Bloom filter is a membership technique: you filter events based on a particular strategy. Filtering here is just applying an if statement — if the element is in the filter, let it through; otherwise drop it. The version studied here lives inside a window, so the question it answers is whether a particular element is a member of the window filter or not: "we will put a question whether a particular element is a member of this filter or not member of our window filter, means window, because we work with windows."
The starting point is storing items as a bit sequence instead of a regular array. Suppose the elements are 1, 5, 6, 9, 11, 21, 30, and we use a single int variable x. One int takes 4 bytes; one byte is 8 bits; so x holds 32 bits. Index those 32 bits from 1 to 32: bit 1, bit 2, and so on up to bit 30, bit 31. Wherever a number appears in our set, set that bit to 1; everywhere else the bit stays 0.
Worked example — seven elements in one variable. Set S = {1, 5, 6, 9, 11, 21, 30}. A single int x = 4 bytes = 32 bits. Reading the 32 positions from 1 to 32, a 1 marks a position whose number is in S:
| Position | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12–20 | 21 | 22–29 | 30 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Bit | 1 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 1 |
Bit 1 = 1, bit 5 = 1, bit 6 = 1, bit 9 = 1, bit 11 = 1, bit 21 = 1, bit 30 = 1; bits 2, 3, 4, 7, 8, 10 stay 0. One 32-bit variable now encodes the whole membership set — four bytes instead of one Boolean per element. To test membership of, say, 5, read bit 5: it is 1, so 5 is present. To test 2, read bit 2: it is 0, so 2 is absent. Sense-check: the cost is fixed at 32 bits no matter how many elements the set holds — the set's memory no longer grows with its size.
Q: Just a quick refresher: is the range 1 to 31, or is it 32? A: 32. An int is 4 bytes, which is 32 bits, so the sequence has 32 positions.
The natural objection: what if an element is bigger than 30, say 60? With this direct notation the set cannot be represented at all. The answer is hashing — you map the value into the 1-to-32 range, which is what Section 14.1.2 does.
Q: If an element is 60, we cannot represent it with this notation, right? A: Right. We need the hashing technique to make it fit within the 32 positions. The filter does not care what the key is; it only cares which position it ends up in.
A student then checked the memory side of the design: instead of creating an array for all those elements, could we use just four bytes of memory with a Boolean bit sequence?
Q: Instead of an array, can we use just four bytes of memory as a Boolean bit sequence? A: That is exactly the design idea. If one integer variable were used to represent small numbers directly, most of its bits would sit at zero — one for the value, everything else zero — which is pure wastage. Coded as a bit sequence, a single variable stores exactly the membership information.
One more clarification matters before moving on: the filter does not count occurrences.
Q: Do we count how many times an element occurs, or just whether it is present? A: Just whether it is present. "We do not need to count occurrences... the purpose of this algorithm is whether that element is present in the filter or not, whether present or not." Counting occurrences is a different logic — that is exactly what the count-min sketch in Section 14.2 does.
Scope: the direct bit-sequence notation assumes the elements are small integers between 1 and 32 — one bit position per possible value. It breaks as soon as an element exceeds the range (60 has no position) or the values are not small integers at all (strings, event IDs). Hashing, in the next subsection, is the fix. It also assumes membership is all you want: no counting and no removal — bits are only ever turned on.
The direct idea is deliberately naive: 32 positions, 32 bits. The rest of the lecture fixes its two limits — range (hashing) and collisions — while keeping the core bargain: bits are cheap, memory stays fixed, and the error stays controllable.
14.1.2 Hashing: Mapping Any Key into the Filter
The direct bit-position trick only works for keys that are small integers. Real keys — event IDs, strings, arbitrary values — can be anything. So the filter never stores the key itself: we hash the key, see which location it lands on, and store a 1 there.
A hash function (a deterministic rule that squeezes an arbitrary key down to one number in a fixed range), written h, maps any key k into the range 1 to m, where m is the filter size. The same key always produces the same output — that is what makes the method reproducible: insert by computing h(k) once, and the membership test later recomputes exactly the same value. To insert a key k, compute h(k) and set bit h(k) to 1. The membership test is the same operation: compute h(k), look at that bit, and report "present" if it is 1.
Worked example — the oversized key. The set needs element 60, but the filter only has positions 1 to 32. Apply the hash: h(60) = 60 mod 33 = 27, so bit 27 goes to 1. Later, to test whether 60 is present, compute h(60) = 27 again and read bit 27: it is 1, so the filter reports present. The key itself is never stored — only the position it lands on. Sense-check: 27 lies inside 1..32, so the mapping always fits; and hashing again gives the same 27, so insertion and test agree.
A student's worry about the range was addressed directly: "we have cells between 1 to 32, but your keys need not be these integer representations. That is the reason we are using hashing, where the hash value will be between 1 to 32 — that is the idea." The exact bound is not sacred; the logic is that the hash maps any key into the filter's range. Values such as 60 are handled the same way, and zero is not a special case — the value only needs to land inside the range.
Notation: the lecture writes the hash output as a position between 1 and m (here 1 to 32), and its examples use a modulus of 33 for a 32-position filter. Standard treatments usually write h(k) mod m and get outputs 0 to m − 1. The idea is identical — a deterministic key-to-position map — only the index shift differs. Keep the lecture's 1..m form for the exam.
One more property drives everything that follows: the hash must spread keys evenly across the positions. In production, streaming systems use fast, non-cryptographic hashes (such as MurmurHash) precisely because they scatter keys well and cost little per key — a necessity at millions of events per window. A weak hash that funnels many keys onto few positions is the direct cause of the problem in the next subsection.
14.1.3 Collisions Cause False Positives
A single hash function has a flaw: two different keys can hash to the same location. That is a collision, and it breaks membership reporting.
Worked example — one bit, two keys. Take a 32-position filter and the hash h(x) = x mod 33. Key k1 = 16: h(k1) = 16 mod 33 = 16, so insert k1 and bit 16 becomes 1. Key k2 = 49: h(k2) = 49 mod 33 = 16 too — a collision; insert k2 and bit 16 stays 1 (it is already set). Now query: ask the filter about k1 → bit 16 is 1 → present, and this is correct. Ask about k2 → bit 16 is still 1 → the filter reports present — but k2 was never inserted. The filter returns the wrong answer for k2: it says "present" for an element that is not there. Sense-check: only one bit flipped in the whole filter, yet the answer for k2 flipped from "absent" to "present" — the entire error comes from the shared bit.
That wrong answer has a name. A false positive (the filter reports that an element is present when it is not) is the error this algorithm trades for memory. Its source is the collision: "due to collision, the filter returns incorrectly the presence of the element. That means even the element is not there, it will show that the element is present."
Notice the direction of the error. Bits only ever go from 0 to 1 — insertion never clears a bit. So the filter can never say "absent" for something that really was inserted: false negatives are impossible. Every error the filter can make is a false positive, and that one-way property is what makes the design safe to use — a wrong "yes" can be re-checked later, while a wrong "no" could drop an event forever.
A student proposed eliminating collisions altogether: use a mod function that uniquely generates a unique hash value for every unique number. The instructor rejected that direction:
Q: Can we use a mod function that uniquely generates a hash value for every unique number, so collisions never happen? A: No. Such perfectly unique functions are computationally expensive — polynomial and multinomial families and the like — and you cannot afford them per event at stream scale. The smart way is to use multiple hash functions instead of one, and check the presence of the element on multiple positions at once.
14.1.4 Multiple Hash Functions and the Mask Check
Use k hash functions; the lecture standard is three. To insert a key x, set bits h1(x), h2(x), and h3(x). To test membership, compute all three values — i1 = h1(x), i2 = h2(x), i3 = h3(x) — and report "present" only if all three bits are 1 at the same time.
Why three instead of one? For a false positive now, every hash function would have to collide simultaneously, and with a large filter that is very unlikely: "when simultaneously they are satisfying this, I will say yes, because simultaneously it is very less likely that every hash function will face this problem, if my filter is very large."
Worked example — the AND check. Suppose the filter has m = 8 positions and currently holds bits {2, 5, 7} set to 1. Query key x with i1 = 2, i2 = 5, i3 = 7: all three bits are 1 → present. Now query key y with i1 = 2, i2 = 5, i3 = 6: bit 6 is 0 → not present, even though two of its three positions happen to be 1. Sense-check: a single 0 among the three positions is enough to say "absent"; only the all-1 case reports presence.
The practical check uses a mask. Build a second bit array with zeros everywhere except the three positions i1, i2, i3, where the mask has 1s. Then AND the filter with the mask. If the AND result is 1 — meaning those three locations were already 1 in the filter — the element is present. All other positions are masked out, so they never matter for this query: "I put everywhere zero except these three positions... when I do the AND operator, if my AND operator is returning 1, definitely only these three locations, anyway it is present — I don't bother about other locations."
The mask is a single cheap bitwise operation per query: one AND of two m-bit arrays. That is the whole cost of membership — two memory reads and one AND — which is why the filter can keep up with millions of events per window.
Assumption: the design assumes the k hash functions are independent — each key's three positions carry no information about one another. That is what makes "all three bits 1" a meaningful signal: with dependent hashes, the k checks would degenerate into one, and the error would return to the single-hash level.
There is no foolproof design. At event scales of 10^6 or 10^7 per window, collisions cannot be eliminated, so the design goal becomes explicit: how do we design a filter that reduces the false positive rate? That is the problem statement the whole analysis in the next sections solves.
Real-world: this is the motivation behind every approximate membership structure in stream platforms — at 10^6 to 10^7 events per window you cannot keep a set of every key, so you pay a controlled false positive rate to get a filter of a few million bits. In online advertising, for example, ad networks keep Bloom filters of seen click and impression IDs to throw away duplicate clicks before they reach billing and bidding systems — they accept a small false positive rate because dropping a few valid events is cheaper than counting fake ones.
14.1.5 False Positive Analysis: The Probability Chain
Three parameters control the filter:
- k — the number of hash functions used in the filter design.
- m — the filter size: the number of positions (bits) the filter can hold.
- n — the occupancy: how many elements are currently stored in the filter right now (not its maximum capacity).
The false positive rate is a probability question, and the lecture builds it as a chain: first one bit and one insertion, then more hash functions, then more insertions. Every step tracks the probability of the bit staying 0, because a false positive is exactly a bit being 1 when it should not be.
Start with a single bit and a single insertion. With one hash function h1, the bit becomes 1 if h1(x) lands on it, which happens with probability 1/m — a uniform hash visits each of the m positions with equal chance. So the probability that the bit is still 0 after one insertion using h1 is:
"Because when you use one hash function, it can occupy any one of the slots of the m."
Now add a second hash function h2. The bit survives if it escapes h1 AND h2. Because the hash functions are independent, the probabilities multiply — the AND of two independent events multiplies their probabilities:
Generalizing to all k hash functions, for one insertion:
That was one insertion. After n insertions we repeat the process n times — each insertion is an independent throw of k darts at the m bits — so the survival probability multiplies n times again:
A false positive happens exactly when the bit a queried key's hash points to is 1 even though the key was never inserted — a bit that is 1 incorrectly. The probability of incorrectly reporting presence for one hash function is one minus the probability the bit is still 0, and the same condition must hold for all k hash functions at once. The total false positive rate is:
The verbal description that produced it: "the total false positive rate is nothing but one minus of one minus 1 by m, power nk, whole to the power of k, because this is the false positive rate due to all k hash functions."
Two quick reality checks on the formula before continuing. With nothing inserted (n = 0), the inner bracket becomes , so FPR = 0 — an empty filter never lies. And as the filter grows (m → ∞), approaches 1, the inner bracket approaches 0, and FPR approaches 0 — an enormous filter never collides. Both limits behave as they must, which confirms the shape of the derivation. (The independence of the hash functions, and of the insertions, are the assumptions quietly baked into every multiplication.)
Exam note: the derivation itself is not asked in the exam, but the process matters — the instructor said the proof is important because problems may be built on it. And the formula that emerges is "very, very important" from the exam point of view.
14.1.6 Approximating the False Positive Rate
The exact form is awkward to work with, so the lecture approximates it using the binomial expansion. Recall the binomial theorem for : it expands as 1 + C(m,1)x + C(m,2)x^2 + ... + C(m,m)x^m, that is:
Apply the same pattern to our term, with x = −1/m and the exponent nk:
where the binomial coefficient is:
Because nk is very large, subtracting 1, 2, and so on barely matters, so C(nk, r) is about (nk)^r / r!. And because nk is so large, the sum may be extended to infinity — the terms beyond r = nk are all negligible:
The infinite sum on the right is exactly the exponential series — this is where the e in the formula comes from. Substituting back into the false positive rate:
Q: In the nCr formula, is the denominator r to the power 1, or r factorial? A: r factorial. The r! is factorial notation, not a power — nCr = n(n−1)...(n−r+1) divided by r!. So C(nk, r) = nk(nk−1)...(nk−r+1)/r!.
The instructor paused after this step so everyone could copy the derivation down, and repeated the outcome.
Exam note: note this formula down, FPR = (1 − e^{−nk/m})^k — very, very important from the exam point of view. The approximation replaces the clunky power with a clean exponential, and every worked problem in this lecture (and the exam) uses this version.
14.1.7 The Optimal Number of Hash Functions and Load Factor
Given m and n, how many hash functions should we use? The optimal k is found by setting:
The intuition: a well-designed filter leaves its bits as uncertain as possible — probability 1/2 of being 0 and 1/2 of being 1. A bit that is almost always 0 is doing no work: it never flips for a queried key, so its position never contributes a false positive — but it also means the filter is far larger than needed. A bit that is almost always 1 makes every query hit a wall of 1s, and false positives multiply. The halfway point — each bit a fair coin flip — is the design sweet spot: "The total probability of identifying a bit is 0 or a bit is 1 is 1/2, 1/2. When I say 1 minus this quantity, you can set this quantity to 1/2. That can be your k optimal."
Take the natural log of both sides:
The log of the exponential cancels the base: on the left, and on the right. Multiply both sides by −m/n:
The instructor quoted ln 2 as about 0.691 (the true value is 0.6931), giving the memorable forms:
where α = n/m is the load factor of the filter — the fraction of the filter that is filled, the percentage of occupancy. (The same answer falls out of minimizing the approximate FPR with respect to k by calculus; the professor's half-and-half argument reaches it without any derivatives.)
Worked example — load factors and hash counts. A filter loaded to 70% has α = 0.7, and the optimal hash count is 0.691/0.7 ≈ 0.987, which rounds to 1 hash function. At 50% load: 0.691/0.5 ≈ 1.38 → 1 to 2 hashes. At 10% load: 0.691/0.1 ≈ 6.91 → about 7 hashes. Sense-check: the emptier the filter, the more hash functions the formula wants — an empty filter can afford several checks per query, while a nearly full filter gains nothing from extra darts, because most bits are 1 anyway.
The same rule is often quoted as a bits-per-element figure: budget c bits per element, use about k = c·ln 2 hash functions, and the expected false positive rate lands near . Eight bits per element, for example, gives about 6 hash functions and a false positive rate near 2% — a standard sizing rule used before deploying a filter.
14.1.8 Worked Problem: False Positive Rate for a 6 MB Filter
The instructor solved the following problem in full, saying the class should know how exam questions will come — "we should know how exam questions will come, that's why I'm showing this." The problem below is the one worked in class: its exact slide wording was partly garbled in the source material, so the four parts (a)–(d) follow the in-class discussion, which was complete.
A Bloom filter with capacity 6 MB stores 10^6 events using 3 hash functions.
Part (a) — the false positive rate for the 6 MB filter.
Step 1 — convert the capacity to bits. In all calculations, assume 1024 ≈ 1000 (10^3). Then 6 MB = 6 × 1024 KB ≈ 6 × 10^3 KB. One KB is 1024 bytes ≈ 10^3 bytes, so 6 × 10^3 KB ≈ 6 × 10^3 × 10^3 = 6 × 10^6 bytes. One byte is 8 bits, so the filter holds:
Step 2 — read the parameters from the statement: k = 3 hash functions; n = 10^6 events currently in the filter; m = 48 × 10^6 bits.
Step 3 — substitute into FPR = (1 − e^{−nk/m})^k (the 10^6 factors cancel, leaving the clean exponent −1/16):
Numerically, e^{−1/16} ≈ 0.9394, so 1 − 0.9394 ≈ 0.0606, and (0.0606)^3 ≈ 0.0002226. As a percentage, that is about 0.02% (0.0223%). The student who worked it on the calculator read "0.000222" — "dot triple zero triple two" — and the instructor confirmed it is essentially 0.02% once multiplied by 100. Sense-check: the exponent 1/16 is small, so e^{−1/16} is close to 1, the bracket is small (about 0.06), and cubing shrinks it further — a 6 MB filter on a million events is very accurate.
Q: Earlier I thought n was the capacity of the filter. Here n = 10^6 is the number currently in the filter at that moment, right? A: Correct. n is the occupancy — how many elements are currently in the filter. At this point in time the filter holds 10^6 events, so n = 10^6. The problem says the filter can store 10^6 events at a period of time; that is the current count, not the maximum it could ever hold.
Part (b) — the false positive rate when the filter capacity is changed to 60 MB. The filter was 6 MB; now m = 480 × 10^6 bits. Because m sits in the denominator of nk/m, the exponent changes:
Since e^{−1/160} ≈ 0.9938 is very close to 1, the bracket is about 0.0062, and 0.0062^3 ≈ 2.4 × 10^{−7}. As a percentage, that is 0.000024%. Sense-check: ten times the capacity multiplied the denominator of the exponent by ten, and because the bracket is cubed, the error fell by a factor of roughly a thousand — 0.0002226 / 2.4 × 10^{−7} ≈ 930.
Part (c) — what conclusion can be drawn from (a) and (b)? The conclusion is that increasing the filter capacity further reduces the false positive rate. More bits, fewer collisions, smaller error.
Real-world: this is the sizing trade-off behind every approximate filter deployment — memory budget versus accuracy. The same formula is what you use to pick a filter size before you deploy, and the 6 MB-to-60 MB comparison is the textbook form of that decision. Production figures land on the same curve: about 9.6 bits per element buy a 1% false positive rate, and under 1 MB tracks a million domain names at under 2.5% error.
14.1.9 Worked Problem: Part D — The Optimal Hash Count and Its Disadvantage
Part (d) of the problem asked about the number of hash functions. Using the optimal count formula:
(The instructor read the result as "around 33.27"; using the rounded constant 0.691 instead of 0.693 gives about 33.17 — the difference is a rounding artifact, not a conceptual one.)
So the formula nominally says: use about 33 hash functions. The obvious trap is to take that number at face value. Thirty-three hash functions means computing 33 hashes for every event, every insertion, every query. The computation is very high, and the performance of the filter deteriorates badly.
Q: If 33 hash functions makes the computation so high, how do we correct it? A: The answer is hidden in part (b): increase the capacity. Even though the formula says increase the hash functions, you should not. The current false positive rate is already far below 1% (0.02%). Adding hash functions may reduce it a little further, but only at the cost of computing hash functions — which adds no value here. The false positive rate is not only a function of k; it also depends on n and m.
Q: If the load factor is 70%, how many hash functions do we need? A: Feed the load factor into k_opt = 0.691/α: 0.691/0.7 ≈ 0.987, so we need about 1 hash function. A heavily loaded filter is already dense with 1s; extra hash functions change little.
The general decision rule the instructor stated: before deciding to increase the number of hash functions, always look at your current false positive rate, and ask whether the increase is worth its computational cost. If the rate is already below 1%, adding hash functions usually buys nothing.
Pitfalls — Bloom filter. (1) Taking k_opt at face value: 33 hashes is the formula's answer, not the deployment's. Check the current false positive rate first — if it is below 1%, extra hashes cost computation and buy nothing. (2) Mixing up n with capacity: n is the occupancy — how many elements are in the filter right now — not the maximum it could hold; the 6 MB problem uses n = 10^6 as a current count. (3) Forgetting the exam convention 1024 ≈ 1000: 6 MB must become 6 × 10^6 bytes before multiplying by 8. (4) Confusing the two structures: the Bloom filter answers presence, the count-min sketch answers frequency — they are separate algorithms.
One last shortcut students often puzzle over:
Q: In the formula, instead of remembering 0.691, can we remember ln 2? ln 2 is 0.693, not 0.691. A: Yes, that is also correct. The constant is ln 2 — about 0.693 — and the difference between 0.691 and 0.693 makes almost no difference in practice.
14.2 Count-Min Sketch
14.2.1 The Sketch Table and the Insertion Rule
Hook: The Bloom filter can tell you an element is present, but it cannot tell you it appeared five times. Keeping exact per-key counters needs memory per distinct key — the count-min sketch gets frequency answers from a tiny table of counters.
Where the Bloom filter answers "present or not?", the count-min sketch answers "how many times?" It estimates frequency: the number of times a particular element has occurred in the stream — "count-min sketch is frequency, that means number of times a particular element present in the filter."
The data structure is a small table called the sketch table. Like every table it has rows and columns; in the lecture example, 3 columns and 4 rows. The columns are the hash functions — one per column — and the rows are the possible hash values, 1 through 4. The cell A_ij holds a counter. (Notation: texts call the number of columns the depth of the sketch and the number of rows the width; the lecture just calls them columns and rows.)
The insertion rule is one line. If the j-th hash function maps key K to row i, written Hj(K) = i, then we increment the counter in cell A_ij: "if I say Hj of my K is equal to i, that means matrix element Aij... it indicates that we should increment the counters."
So the procedure for one arriving event is: for each hash function j = 1, 2, 3, compute the row i = Hj(K), and add 1 to the counter in cell (i, j). Nothing else happens at insert time — no comparisons and no bookkeeping of the key itself.
Worked example — first insertion. The key "History of Japan" hashes as H1 = 1, H2 = 2, H3 = 1. So we increment cell (1,1) — row 1, column 1 — cell (2,2), and cell (1,3). Each of those counters goes from 0 to 1.
| Cell | Counter |
|---|---|
| (1,1) | 1 |
| (2,2) | 1 |
| (1,3) | 1 |
Sense-check: three hashes, three cells, three increments; every other cell of the table is untouched and stays 0.
14.2.2 Worked Insertions: Three Keys, Three Rows of Counters
Worked example — second insertion. A second key arrives: "European history", which hashes to (1, 1, 4). The cells to increment are (1,1), (1,2), and (4,3). Cell (1,1) is already 1, so it becomes 1 + 1 = 2. Cell (1,2) was empty — it becomes 1. Cell (4,3) was empty — it becomes 1.
Now "History of Japan" appears again — a third insertion. It hashes to the same places as before: (1,1), (2,2), and (1,3). Cell (1,1) goes 2 → 3, cell (2,2) goes 1 → 2, cell (1,3) goes 1 → 2.
After three insertions the table reads:
| Cell | Counter |
|---|---|
| (1,1) | 3 |
| (2,2) | 2 |
| (1,3) | 2 |
| (1,2) | 1 |
| (4,3) | 1 |
Or, drawn as the full 4-row by 3-column grid (rows are hash values 1–4, columns are hash functions H1–H3):
| H1 | H2 | H3 | |
|---|---|---|---|
| row 1 | 3 | 1 | 2 |
| row 2 | 0 | 2 | 0 |
| row 3 | 0 | 0 | 0 |
| row 4 | 0 | 0 | 1 |
The pattern to notice: a key that appears multiple times keeps hitting the same cells, so its cells accumulate the largest counts. But other keys also touch those cells — (1,1), for example, was bumped by both "History of Japan" and "European history" — which is exactly the pollution the query rule below defends against. Notice also that no cell's value equals a single key's count: (1,1) holds 3, yet no key appears three times in the stream.
Real-world: this is how streaming systems estimate heavy hitters and top-k style aggregates without storing the event stream — every event touches only a handful of counters. In production, the counter table is usually paired with a small heap of candidate keys, so the system can report "the 10 most frequent keys" on demand.
14.2.3 Querying Frequency: Take the Minimum
To find the frequency of a key, hash it with all three hash functions, read the three cells the hashes point to, and report the minimum of the three counters.
Q: When do we actually compute the minimum? So far we have only been incrementing. A: Incrementing happens at insert time; the minimum is computed at query time. Suppose the query is "find the frequency of hello". Let H1(hello) = 1: the cell is (1,1), which holds 3. Let H2(hello) = 1: the cell is (1,2), which holds 1. Let H3(hello) = 4: the cell is (4,3), which holds 1. (The instructor first said H3(hello) = 1, then corrected it to 4 mid-example.) The three readings are 3, 1, and 1, and the minimum among them is 1 — so the query concludes that the occurrence count of "hello" is 1.
Check the same procedure against the two inserted keys: "History of Japan" reads (3, 2, 2) → minimum 2, its true count; "European history" reads (2, 1, 1) → minimum 1, its true count. The method gives exact answers for the keys that were inserted, and a wrong-but-nonzero guess (1 instead of 0) for the never-inserted "hello".
Q: Why do we take the minimum, and not the average or the largest? A: Because the sketch is a heuristic — you cannot store the entire stream of events as they come into your count-min window. Other keys can only inflate a cell above the true count; no key can ever make a cell smaller than its own contribution. So the smallest reading is the safest estimate: any collision inflates the count, and the minimum is the reading that resists that inflation the most.
That reasoning gives the sketch's core guarantee: the answer never underestimates the true count — every reading is the true count plus some non-negative collision noise, so the minimum is noise-resistant by construction. With a wide enough table and enough rows the overestimate stays small; the original paper shows the estimate stays within a small error of the true count with high probability when the width and depth are chosen properly.
Q: How do we compute the hash values like (1, 1, 4) that the examples use? A: They are given in the problem, not computed — and that is also the exam standpoint. You do not have to calculate the hash values unless it is specified. If a question needs you to compute them, the way to compute them will be specified.
Pitfalls — count-min sketch. (1) Taking the minimum at insert time: insertion only increments counters; the minimum is a query-time operation. (2) Reading the wrong cell: rows are hash values (1 to 4), columns are hash functions (H1, H2, H3) — cell (i, j) means row i, column j. (3) Reading a high cell as one key's count: (1,1) = 3 does not mean some key appeared 3 times; several keys can share a cell. (4) Trying to compute hash values in the exam: they are given in the problem unless the method is specified.
The two structures answer the two questions, and the exam asks you to keep them apart:
| Bloom filter | Count-min sketch | |
|---|---|---|
| Question answered | "Is the element present?" | "How many times has it appeared?" |
| Structure | One bit array of m bits | A table of counters (rows × columns) |
| Insert | Set k bits to 1 (one per hash) | Increment k counters (one per hash) |
| Query | All k bits 1? → present | Minimum of k counters → frequency |
| Error | False positives (says present when absent) | Overestimates (says more than the true count) |
| Never happens | False negatives (absent when present) | Underestimates (below the true count) |
One-sentence rule: use the Bloom filter when the window question is about membership, and the count-min sketch when it is about frequency — and remember which error each one can make.
Recap: the count-min sketch answers "how many times?" with a table of counters, increments at insert time, and reports the minimum of the key's cells at query time. It only ever overestimates, so the minimum is the safest estimate. The Bloom filter and the count-min sketch are the two windowed algorithms to master side by side — presence versus frequency.
Exam Guidance Summary
- The false positive formula is the headline. FPR = (1 − e^{−nk/m})^k is "very, very important" from the exam point of view — note it down and be ready to substitute numbers into it. Know what each parameter means: m is the filter size in bits, n is the occupancy (elements currently in the filter), k is the number of hash functions.
- The derivation is process material. The probability chain, then the binomial expansion to the exponential, is not asked as a proof in the exam, but you should remember how the process runs — the proof matters because problems may be built on it. A OneNote containing the step-by-step derivation of the Bloom filter formula was shared for revision, and a review session follows this one.
- Expect four-part worked problems. Worked problems of the kind solved here — capacity in MB, events count, hash count, parts (a) through (d) — show how exam questions come. Expect to compute a false positive rate, compare capacities, state a conclusion, and discuss the number of hash functions.
- Use the 1024 ≈ 1000 convention. In all calculations, assume 1024 ≈ 1000 (10^3): 6 MB becomes 6 × 10^6 bytes, then 48 × 10^6 bits at 8 bits per byte.
- Optimal hash count: k_opt = (m/n) ln 2 = 0.691/α, with α = n/m the load factor. A 70% loaded filter needs about 1 hash function. If the current false positive rate is already below 1%, adding hash functions usually buys nothing.
- Do not confuse the two structures. The Bloom filter answers presence; the count-min sketch answers frequency. Each has its own query rule: all-k-bits for the filter, minimum of the cells for the sketch.
- Count-min sketch hashes are given. Hash values are given in the problem and are not computed in the exam unless the question specifies how to compute them.
- Revision focus: the Bloom filter, the count-min sketch, and the decaying window algorithm (covered previously) are the important algorithms; also go through the lecture notes and structured streaming.
Key Industry Applications
- Membership and frequency at stream scale. Membership filtering and frequency counting at event scales of 10^6 to 10^7 per window are exactly the workloads stream-processing platforms handle; the Bloom filter and count-min sketch are the memory-efficient answers. Every event touches only a handful of bits or counters, which is the only way a server can keep up with millions of events per minute.
- Sizing decisions in production. The false positive formula drives deployment sizing — the 6 MB versus 60 MB comparison in the worked problem is the same memory-versus-accuracy trade-off made in production. Online advertising is a live example: networks keep Bloom filters of seen click and impression IDs to drop duplicate clicks before billing and bidding, accepting a small false positive rate because a few lost valid events cost less than counting fake ones.
- Frequency estimation without storing the stream. The count-min sketch's counter-table design is how stream systems estimate frequencies and heavy hitters without keeping the full event stream, touching only a few counters per event. Pairing the table with a small heap of candidate keys turns it into a top-K or heavy-hitters engine — the standard way streaming platforms surface the most frequent items in a window.
SPA Lecture 14 notes · Bloom Filters and Count-Min Sketch
Sections Breakdown
The bit-sequence membership idea, hashing, collisions and false positives, the multi-hash mask check, the false positive rate derivation and its exponential approximation, the optimal hash count, and the 6 MB worked problem.
The sketch counter table, the insert-time increment rule, worked insertions with three keys, and the query-time minimum rule.
Exam strategy: the false positive formula, four-part worked problems, the 1024 ~ 1000 convention, and the optimal hash count rule.
Real-world membership filtering, filter sizing decisions, and frequency estimation without storing the stream.
Exam Revision Notes
Below is the distilled, exam-ready core. Every entry comes from the full explanation above. Use this section for rapid review; return to the main notes when a point needs more context.
Bloom Filter
Must-know: Bloom filter reports presence only; false positives possible, false negatives impossible. FPR = (1 - e^(-nk/m))^k with m = filter bits, n = current occupancy, k = hash count. More capacity shrinks the error; a 6 MB filter with 10^6 events and 3 hashes gives about 0.02%.
⚠️ Top pitfall: Taking k_opt = 33 at face value: check the current false positive rate first; increasing capacity beats adding hash functions when the rate is already below 1%.
Self-check: A 6 MB filter stores 10^6 events with 3 hash functions. Convert the capacity to bits and compute the false positive rate.
Connects to: Count-Min Sketch
Count-Min Sketch
Must-know: Count-min sketch estimates frequency: increment one counter per hash function at insert time; at query time hash the key, read the counters, and report the minimum. The estimate never underestimates the true count; hash values are given in the problem, not computed.
⚠️ Top pitfall: Taking the minimum at insert time (it is a query-time operation) and reading a high cell as one key's count when several keys can share a cell.
Self-check: After inserting 'History of Japan' (1,2,1), 'European history' (1,1,4), and 'History of Japan' again, what frequency does the sketch report for 'hello' with hashes (1,1,4)?
Connects to: Bloom Filter
Exam Guidance Summary
Must-know: Memorize FPR = (1 - e^(-nk/m))^k and k_opt = (m/n) ln 2 = 0.691/alpha; use 1024 ~ 1000 in capacity conversions; Bloom filter answers presence, count-min sketch answers frequency.
⚠️ Top pitfall: Confusing the presence question with the frequency question, or using n as capacity instead of current occupancy.
Self-check: Which formula do you use to size a filter before deployment, and what does each parameter stand for?
Connects to: Bloom Filter, Count-Min Sketch
Key Industry Applications
Must-know: Stream platforms use Bloom filters for cheap membership checks (e.g., deduplicating ad clicks) and count-min sketches for frequency and heavy hitters without storing the stream.
Self-check: Why can an ad network accept a small false positive rate in its duplicate-click filter?
Connects to: Bloom Filter, Count-Min Sketch
Was this lecture useful?
BitsNotes AI Assistant
Subject Notes AssistantConfigure AI Chat
Choose how to access the chatbotSigned in as
Powered by BitsNotes — 20 messages per day. No API key needed. Want unlimited access? Use "Bring Your Own Key" mode.
Sign in to use AI Chat
Get 20 free AI messages per day to ask questions about your lecture notes. Sign in with Google or GitHub — it takes 5 seconds.
Sign In to BitsNotesSwitch to "Bring Your Own Key" tab above for unlimited access with any OpenAI-compatible provider.