Overview
Probabilistic data structures answer questions about huge data sets using tiny, fixed memory by accepting a small, controlled error. A Bloom filter tests set membership ('definitely not present' or 'probably present'). HyperLogLog estimates the number of distinct items (unique visitors) within about 1% using kilobytes. Count-Min Sketch estimates how often each item appears (heavy hitters).
They matter at scale. Tracking 1 billion unique user IDs exactly needs gigabytes; a HyperLogLog needs about 12 KB. Databases, caches, CDNs, and analytics systems use these structures to skip unnecessary disk reads, count uniques, and find trending items in streams.
Instead of carrying a thousand-page guest list, the bouncer has a small card of marks. If your marks are not all on the card, you are definitely not invited. If they are, you are probably invited, so check the full list just to be sure.
When to use it
- Avoiding expensive lookups for items that definitely do not exist.
- Counting unique visitors, searches, or devices at massive scale.
- Finding top-K or trending items in a stream.
- Situations where approximate answers are acceptable and memory is tight.
Where it shows up in interviews
Recognize it when: most lookups are for items that do not exist.
- Design a web crawler (seen URLs)
- Design a username availability check
- Prevent cache penetration
Recognize it when: unique counts or frequencies over billions of events.
- Design YouTube view counting
- Design trending hashtags
- Design an ad click aggregator
Where it is used in real software
Use Bloom filters per SSTable to skip files that cannot contain a key, saving disk reads.
Provides HyperLogLog (PFADD, PFCOUNT) for unique counts in 12 KB per key, plus Bloom and Count-Min modules.
BigQuery, Druid, and Presto use HyperLogLog for fast APPROX_COUNT_DISTINCT queries.
Key terms
- False positive
- The structure says 'maybe present' when the item is absent (Bloom filter).
- False negative
- Saying 'absent' when present; Bloom filters never do this.
- Bloom filter
- Bit array plus k hash functions for membership tests.
- HyperLogLog
- Estimates cardinality from the maximum run of leading zeros in hashed values.
- Count-Min Sketch
- Grid of counters giving upper-bound frequency estimates.
How it works, step by step
- 1Choose the question
Membership (Bloom), distinct count (HyperLogLog), or frequency (Count-Min Sketch).
- 2Choose acceptable error
For example 1% false positives, which determines memory and hash count.
- 3Hash items
Use fast, well-distributed hash functions (MurmurHash, xxHash).
- 4Update on each event
Set bits, update registers, or increment counters in O(1).
- 5Query and verify when needed
Treat 'maybe' as a hint and confirm with the source of truth if exactness matters.
STEP 1Empty filter, all bits are 0.
Exact vs probabilistic
1 billion distinct user IDs (16 bytes each)
| Question | Exact structure | Memory | Probabilistic | Memory | Error |
|---|---|---|---|---|---|
| Seen this ID? | Hash set | ~16+ GB | Bloom filter (1% FP) | ~1.2 GB | 1% false positives |
| How many unique? | Hash set | ~16+ GB | HyperLogLog | ~12 KB | ~0.8% standard error |
| How often was X seen? | Hash map of counts | ~24+ GB | Count-Min Sketch | few MB | Overestimates only |
NOWQuestion: Seen this ID? | Exact structure: Hash set | Memory: ~16+ GB | Probabilistic: Bloom filter (1% FP) | Memory: ~1.2 GB | Error: 1% false positives
Orders-of-magnitude memory savings in exchange for small, bounded, one-sided errors.
Implementation
// Bloom filter with double hashingclass BloomFilter { private bits: Uint8Array; constructor(private size: number, private hashes: number) { this.bits = new Uint8Array(Math.ceil(size / 8)); } private hash(value: string, seed: number) { let h = 2166136261 ^ seed; // FNV-1a variant for (let i = 0; i < value.length; i++) { h ^= value.charCodeAt(i); h = Math.imul(h, 16777619); } return h >>> 0; } private positions(value: string) { const h1 = this.hash(value, 0), h2 = this.hash(value, 0x9747b28c) || 1; return Array.from({ length: this.hashes }, (_, i) => (h1 + i * h2) % this.size); } add(value: string) { for (const p of this.positions(value)) this.bits[p >> 3] |= 1 << (p & 7); } mightContain(value: string) { return this.positions(value).every((p) => (this.bits[p >> 3] & (1 << (p & 7))) !== 0); } // Optimal sizing for n items and false-positive rate p static forCapacity(n: number, p: number) { const m = Math.ceil((-n * Math.log(p)) / Math.LN2 ** 2); return new BloomFilter(m, Math.max(1, Math.round((m / n) * Math.LN2))); }} const seen = BloomFilter.forCapacity(1_000_000, 0.01); // ~1.2 MBseen.add("https://example.com/a");seen.mightContain("https://example.com/a"); // trueseen.mightContain("https://example.com/z"); // false (almost always)Complexity and performance
Constant time regardless of data size.
Independent of item size.
For billions of distinct items at ~0.8% error.
Trade-offs
Lower error rates need more memory and hash functions; choose error bounds from business needs.
Standard Bloom filters cannot delete items or list members; use counting Bloom filters or cuckoo filters if deletion is required.
Variants and related techniques
Membership test that supports deletion with similar space efficiency.
Maintain a small heap of candidates to find trending items in streams.
Common mistakes
- Treating 'maybe present' as certain.
Fix: Confirm with the source of truth when correctness matters.
- Undersized Bloom filters.
Fix: False positives explode as the filter fills; size for expected capacity and rebuild as it grows.
Interview questions
How would you avoid hitting the database for usernames that do not exist?
Keep a Bloom filter of existing usernames. If it says the name is absent, it is definitely available and needs no database check; if it says maybe present, confirm with the database. False positives only cost an extra lookup.
How do you count unique daily visitors for billions of events?
Use a HyperLogLog per day (for example Redis PFADD/PFCOUNT). It estimates cardinality with about 1% error in 12 KB and can be merged across days or servers.
Practice problems
| Problem | Difficulty | What it trains |
|---|---|---|
| Implement a Bloom filter and measure its false-positive rate | Medium | Sizing and hashing. |
| Design trending hashtags with Count-Min Sketch | Hard | Streaming top-K. |