Back to the map

Module 10 · Data structures for scale · Lesson 9 of 11

HyperLogLog

Counting distinct items in a stream of billions with about 12 KB and 1 percent error. The leading-zeros intuition, why many registers and a harmonic mean fix the variance, merging across servers and windows.

DeepNice-to-knowSenior 12 minPro Updated Nov 2026

Why it matters

"How many unique visitors did this page get today" is an innocent question that is brutal at scale: exact counting needs a set of every visitor id, gigabytes per metric, multiplied by every page, every window and every dimension you want to slice by, and sets from different servers must be unioned. HyperLogLog answers within about 1 percent using 12 KB per counter, merges by taking a maximum, and is why analytics systems can offer unique counts for anything. It is one of the most elegant ideas in practical computer science and a favourite interview question.

The idea

Hash every item to a uniformly random bit string. In a random bit string, the probability that the first set bit appears at position p is 1 over 2 to the p. So if, among all the distinct items you have seen, the longest run of leading zeros is about 20, you have probably seen about 2 to the 20, a million, distinct items. Duplicates hash identically and change nothing, which is what makes it count distinct rather than total.

That single estimate is wildly noisy, one unlucky hash doubles it. HyperLogLog fixes it by splitting the stream into m registers using the first b bits of the hash (m equals 2 to the b, typically 16,384), tracking the maximum leading-zero count in each register, and combining the m estimates with a harmonic mean, which tames outliers. Error is about 1.04 divided by the square root of m: about 0.8 percent at 16,384 registers, which at 6 bits per register is 12 KB.

itemuser 4821hash0010110…|00001011…register bits | run of zeros = 4register 0x0B6: max(3, 4) → 435426345… 16,384 registers, 6 bits each = 12 KBestimate ≈ α · m² / Σ 2^(−register) error ≈ 1.04 / √m ≈ 0.8%
The first 14 hash bits choose a register; the register keeps the longest run of leading zeros seen in the remaining bits. Thousands of registers averaged harmonically give a stable estimate.

Corrections that make it practical

Small cardinalities (fewer items than registers) are estimated badly by the formula, so implementations switch to linear counting (count empty registers) at the low end; very large ones hit hash-collision limits with 32-bit hashes, so 64-bit hashes are used. Redis stores small sets in a sparse encoding of only the non-zero registers and switches to the dense 12 KB form as they fill, so counting ten items costs bytes, not kilobytes. HyperLogLog++ adds bias correction tables for the mid range.

Merging

The union of two sets is estimated by taking, register by register, the maximum of the two sketches. The result is exactly the sketch you would have built from the combined stream. So per-server sketches merge at an aggregator, per-hour sketches merge into days, and per-page sketches merge into a site total, all without ever seeing the raw ids. Intersections are not directly supported; they are estimated via inclusion-exclusion with much worse error, so if you need "users who did A and B", keep separate exact sets or a different structure.

Where it lives

Redis PFADD, PFCOUNT and PFMERGE are HyperLogLog. Every analytics warehouse offers approximate distinct counts (APPROX_COUNT_DISTINCT, HLL_COUNT) built on it; many store sketches as columns so unique counts can be re-aggregated across any dimension later. Observability systems use it for unique series and unique hosts; databases use it in query planners to estimate distinct values for a column.

QuestionStructure
How many distinct items?HyperLogLog
How many times did this item occur?Count-Min sketch
Is this item present?Bloom or cuckoo filter
Which items are most frequent?Count-Min plus a top-k heap
How similar are two sets?MinHash, next lessons
HyperLogLog · Backplane