Representative interview topic

Coding interview: How do you sample uniformly from an unknown-length stream?

CodingMedium
OfferCC Editorial TeamPublished Updated

Question

The stream length is unknown and may not fit in memory. Design a one-pass algorithm that samples k distinct items uniformly at random using O(k) extra space. Explain k=1 and general k, prove every seen item has final probability k/n of being sampled, and discuss randomness, empty streams, and weighted sampling.

Prompt and use cases

You can read the stream only once; its length n is unknown, and you need k distinct items with equal probability. You cannot store the stream or wait for a final random index. Reservoir sampling keeps a fixed reservoir of size k: when item i arrives, it enters with probability k/i and replaces a uniformly chosen reservoir slot.

This prompt tests a randomized streaming algorithm. Vitter’s paper studies one-pass sampling when the population size is unknown, and university course notes give the uniformity induction. The core category is coding: probability invariants under a memory bound, not a data-platform implementation.

What the interviewer evaluates

  • Whether you recognize the unknown-size, one-pass, fixed-memory reservoir pattern.
  • Whether you explain the k=1 1/i replacement rule before generalizing to k.
  • Whether you prove that after item i, every item has probability k/i in the sample.
  • Whether you avoid duplicate samples, a false known-n assumption, and biased random integers.
  • Whether you state O(n) time, O(k) space, and the boundary of weighted sampling.

Clarifications before answering

  • Is k a positive integer? What should happen for k <= 0 or fewer than k stream items?
  • Does “distinct” mean distinct records or deduplication by value?
  • Can the stream be empty, infinite, or interrupted? Output and recovery differ.
  • Is the final reservoir the only output, or must it be observable during the scan?
  • Does the random API provide unbiased integers over the required range?
  • Is the target uniform or weighted/stratified? Weighted sampling needs a different invariant.

30-second answer framework

“Fill the reservoir with the first k items. For item i, starting at one, generate an unbiased integer j in [0, i-1]. If j < k, replace reservoir[j]; otherwise discard the item. After item i, every item has probability k/i: the new item enters with k/i, and an old item survives with k/(i-1) times 1 - 1/i. The algorithm is one pass, O(n) time, and O(k) extra space.”

Step-by-step deep answer

Step 1: Start with k=1.

Keep the first item. For item i, replace the current candidate with probability 1/i. After processing i items, each has probability 1/i of being retained.

Step 2: Generalize to k.

Fill the first k slots. For item i, enter with probability k/i; if it enters, choose one of the k slots uniformly. An integer j in [0, i-1] implements this: j < k means replace slot j.

Step 3: Write the pseudocode.

text
reservoir = first k items
for i = k+1 .. n:
  j = uniformInteger(0, i-1)
  if j < k:
    reservoir[j] = item i
return reservoir

If the stream cannot be prefilled, append while seen <= k, then use the same branch. The integer generator must cover the complete range without bias.

Step 4: Prove the new item’s probability.

At item i, its inclusion probability is k/i. Once included, it survives every later step with probability ∏(1 - 1/t) = i/n, because a particular slot is replaced with probability 1/t. Its final probability is therefore k/i × i/n = k/n.

Step 5: Prove the old item’s probability.

Assume each old item has probability k/(i-1) after item i-1. At step i, it is replaced with probability k/i × 1/k = 1/i, so it survives with 1 - 1/i. Its new probability is k/(i-1) × (i-1)/i = k/i. The new and old items satisfy the same invariant.

Step 6: Analyze complexity and random generation.

Each item is processed once: O(n) time. The reservoir holds k items: O(k) extra space. For counts beyond safe integer precision, use an unbiased integer API that supports the required range.

Step 7: Handle input boundaries.

An empty stream returns an empty sample. k = 0 returns an empty sample or raises the documented error. If fewer than k items arrive, return the actual items or fail according to the contract. Deduplication by value requires extra state and may violate O(k).

Step 8: Explain weighted and distributed extensions.

Weighted sampling changes the target distribution, so equal-probability replacement is invalid; discuss weighted-reservoir keys such as Efraimidis–Spirakis. Distributed reservoirs need counts and priorities/weights to merge correctly; concatenating shard samples is biased.

High-quality sample answer

“I maintain a reservoir of capacity k. Fill it with the first k items. For item i = k+1 onward, draw an unbiased integer j in [0, i-1]; if j < k, replace slot j, otherwise discard. The new item enters with probability k/i. A specific old item is replaced with probability 1/i, so its probability changes from k/(i-1) to k/(i-1) × (1-1/i) = k/i. By induction every item has final probability k/n. The algorithm is one pass, O(n) time, and O(k) space. I test empty input, k=1, k=0, repeated records, repeated simulations, and explicitly re-derive the invariant for weighted or distributed variants.”

Common mistakes

  • Store the entire stream first → violates unknown-size and memory constraints → update the reservoir online.
  • Use 1/k for every new item → probabilities do not adapt to i → use k/i.
  • Use random() % i → modulo can be biased → use unbiased integer sampling.
  • Choose replacement slots non-uniformly → some combinations become more likely → select among k slots uniformly.
  • Use k/n while scanning → n is unknown and the probability changes each step → use the current count i.
  • Ignore duplicate semantics → distinct records and distinct values differ → clarify deduplication first.
  • Concatenate shard reservoirs → unequal shard sizes bias the result → merge with counts and priorities.
  • Reuse the uniform algorithm for weights → the target distribution changed → use weighted reservoir sampling with a new proof.

Follow-up questions and responses

Follow-up 1: Why is the new item’s replacement probability k/i?

At item i, the algorithm chooses one of i positions uniformly. The first k positions represent the reservoir, so the chance of hitting one is k/i.

Follow-up 2: How do you prove fairness for k=1?

The first item is kept with probability one. Item i replaces it with 1/i; any old item survives with (1/(i-1)) × (1-1/i) = 1/i, giving the induction.

Follow-up 3: How do you generate an unbiased integer?

Use a uniform-integer API, or rejection sampling that discards random values outside the largest divisible range. Do not assume simple modulo is always unbiased.

Follow-up 4: What if the stream ends before k items?

Return the actual items or raise the documented error. Never fabricate entries; state the behavior before coding.

Follow-up 5: How would you sample with weights?

Define the weighted target distribution, then use weighted-reservoir random keys or exponential/log transforms. The uniform k/i proof no longer applies directly.

Follow-up 6: How would you merge distributed reservoirs?

Each shard carries its item count and enough random priority or weight information. Merge according to the global sampling rule; direct concatenation or random truncation favors smaller shards.

Follow-up 7: How do you validate uniformity?

Run many trials on a fixed short stream, compare each item’s inclusion frequency with k/n, and test boundaries and reproducible seeds. Statistics can reveal bias, but they do not replace the probability proof.

Public sources

Related questions

Resources

Product Comparison

Every comparison is based on official sources and includes a review date.

Open the full comparison

Related interview tool

Use Screenshot for a coding prompt

Capture the problem, then work through the constraints, solution, code, edge cases, and complexity in order.

View the tool