workshop private

← all creations

Keep

viz · created 2026-09-26

Reservoir sampling drawn as the two curves whose product is the answer — the admission window closing like k/i as the stream grows, the survival odds rising like i/n for exactly the same reason, and a histogram underneath that measures the flat line they multiply to instead of asserting it.

algorithmsinterview-prepcanvas

You are handed a stream of unknown length and k slots, one pass, no rewinding. Reservoir sampling says: let the first k in for free, then admit item i with probability k/i, overwriting a resident chosen uniformly at random. At the end, every item that went past is in your hands with probability exactly k/n — and you never needed to know n.

The part worth drawing is not the answer. It is that item 200 is admitted with probability 5% while item 11 is admitted with probability 91%, and they still come out even. Two quantities decay in opposite directions and the product is a constant:

(k/i)(i/n) = k/n, for every i. The why band draws both curves and then draws their product pointwise rather than announcing it — a falling hyperbola, a rising line, and a flat one underneath that never moves. Watch the right-hand edge: at i = n the entry curve lands on the product line, because the last item has no survival to do.

The measurement

One pass is one draw and proves nothing, so the bottom band is the experiment. +1000 passes runs complete streams and tallies, per stream position, how often it ended up in the final sample. The dashed line is k/n. The shaded strip is ±2σ — the noise that the number of passes alone buys you — so a bar outside it means something, and a bar inside it means nothing. The header prints the worst position in units of that σ, next to the largest deviation you should expect from n positions when nothing is wrong (~√(2 ln n), about 4.3σ for n = 200). Bias and noise get told apart by eye rather than by vibes.

At n = 200, k = 10, 50,000 passes:

policyhead ÷ tailworst positionχ² (df 199)
Algorithm R1.0073.2σ199
admit k/i, evict the oldest0.00151σ186,085
admit with fixed p = 0.50.000464σ2,174,643
keep the first k∞975σ10,000,000

χ² of 199 on 199 degrees of freedom is as flat as a measurement is allowed to get. The other three are not close, and the shape of how they miss is the interesting part.

The near-miss worth the whole piece

admit k/i — but evict the oldest is the one to spend time on. It computes the admission probability exactly right. It is one line different from the correct algorithm, it is the line a reviewer’s eye slides over, and its histogram is a hill: the first tenth of the stream is at 0.00 of k/n, the middle peaks at 1.68×, and the tail settles back near 1.05×. Not “prefers recent data” — something stranger, because early items are evicted in guaranteed order while late items never get the chance to be old.

Uniform eviction is a second requirement, not an implementation detail of the first. That is why the why band refuses to draw a survival curve for any policy but Algorithm R: without uniform eviction there is no clean i/n to pair the hyperbola with, and the identity that makes the thing work simply does not exist.

The other two failures are loud by comparison. A fixed p ignores the length of the stream and the last decile ends up at 6.4× its share. Keeping the first k is the one everybody knows is wrong, and it is here so the histogram has a cliff in it for scale.

Reading the picture

Controls: policy, n (60 / 200 / 1000), k (1 / 4 / 10 / 25), and p for the fixed-probability policy. t adds passes, s steps one item, space runs the pass, f finishes it, p cycles policy, r resets. Click a slot for its history and its measured inclusion rate. Try k = 1, n = 1000: one slot, a 0.1% window by the end, and the histogram still flattens.

Reuse

src/reservoir.js is a framework-free ES module. Mutating calls return a trace rather than a bare answer, because the probability a policy used and the roll it made are the interesting part:

Gotchas