skip to content

How would you size the seen-set for deduplicating a billion-row clickstream, and what changes when it exceeds RAM?

level: seniorimportance: should knowfreq 40%

answer

  1. start with bytes per distinct key
  2. tables keep empty slots on purpose
  3. a billion times tens of bytes
  4. same key always hashes to one bucket
  5. exactness can be traded for bits

basics

~20 s

Size it as distinct keys times realistic bytes per entry, not raw key bytes: a billion 16-byte keys lands in tens of gigabytes. Above RAM, partition by key hash, order the data so duplicates meet, or accept an approximate filter.

solid answer

~50 s

Start with arithmetic, not with a structure. A billion distinct 16-byte keys is 16 GB of raw key bytes; a keyed lookup structure adds per-entry bookkeeping and keeps a fraction of its slots empty so probes stay short, so the realistic figure is two to four times that — tens of gigabytes. Once that exceeds the box, three moves are available. **Partition**: route rows by a hash of the key into k groups and dedupe each independently, since identical keys always land in the same group; the price is an extra pass. **Order**: bring duplicates adjacent, so one sequential scan holding a single previous key suffices and memory is bounded by a buffer, paid for in I/O. **Approximate**: a compact membership filter costs a few bits per element and never reports a false negative, so it works wherever false positives are tolerable or confirmed exactly.

go deeper

for a junior

Know that remembering which keys you have seen costs memory proportional to the number of distinct keys, and that a billion of anything is a number you should convert to bytes before believing it fits.

for a middle

Explain why bytes per entry exceeds the key size: stored key, per-entry bookkeeping, and the empty slots a lookup structure keeps so probes stay short. Then multiply and compare against the machine.

for a senior

Show the recovery path once the arithmetic fails: partition by key hash so duplicates stay together, or order the data so they become adjacent, and state which resource each choice spends instead of memory.

for a principal

Own the requirement itself. Decide whether exact global deduplication is what the business needs or whether a bounded window or an approximate filter is sufficient, and what that choice costs in correctness, hardware and operational complexity.

## Step one: convert the structure into bytes The defining senior move here is refusing to reason in asymptotics. "O(n) auxiliary space" is true and useless; the job either fits on the machine or it does not, and only arithmetic decides that. Take a billion rows keyed by a 16-byte identifier, worst case all distinct: | Component | Estimate | | --- | --- | | Raw key bytes | 10⁹ × 16 B = 16 GB | | Per-entry bookkeeping (link/metadata/alignment) | commonly 8–32 B per entry | | Slack from load factor (empty slots kept for fast probes) | typically 30–50% more slots than entries | | **Realistic total** | **roughly 40–60 GB** | The two multipliers people forget are the last two. A keyed lookup structure is fast precisely because it is not full: it keeps spare slots so that finding a key takes a short, bounded search, and it grows itself before it fills up. That headroom is not waste to be optimized away — it is what buys the expected-constant lookup — but it means the memory number is a multiple of the key bytes, never equal to them. A candidate who answers "16 GB, so it fits in a 32 GB box" has made the classic error and will discover it in production. The same census applies to the neighbouring tools, and it is worth having the numbers ready: a per-element visited marker over the same data is one bit or one byte per element only if elements are densely indexed; a frequency map is bounded by distinct keys, not rows; a two-dimensional table over two sequences of length n and m is n·m entries, which crosses gigabytes at surprisingly modest n. ## Step two: when it does not fit ### Partition by key hash Send every row to one of k buckets by hashing its key. Identical keys hash identically, so every duplicate pair lands in the same bucket — which means each bucket can be deduplicated completely independently, and the peak memory is the largest bucket's distinct-key count rather than the whole stream's. With k chosen so that each bucket's set comfortably fits, a job that could not run at all becomes k jobs that can, either sequentially on one machine or spread across several. The price is an extra full read and write of the data to form the buckets, plus sensitivity to skew: if one key or one hash range dominates, that bucket is still too big and you either subdivide it or handle the hot keys specially. ### Bring duplicates adjacent by ordering If the rows are ordered by key, duplicates are neighbours, and a single sequential scan holding exactly one previous key emits the distinct rows — working memory drops to a buffer plus one key, independent of how many distinct keys exist. The memory problem is genuinely solved; the cost has moved into ordering data that does not fit in memory, which is paid in reads and writes rather than resident bytes. That is the trade to state explicitly in the interview: the set-based approach is one pass and lots of memory, the ordered approach is bounded memory and more I/O. (The accounting of how many passes the ordering itself takes is its own subject — say that it is the dominant cost and move on.) ### Trade exactness for bits A compact probabilistic membership filter stores a few bits per element instead of the whole key, cutting a tens-of-gigabytes structure to about a gigabyte. It never reports a false negative — anything it says is new really is new — but it may report a false positive, wrongly claiming an unseen key was already seen. That asymmetry decides whether it is usable: if dropping a genuinely-new row occasionally is acceptable (traffic estimation, sampling), the filter alone is enough; if it is not, use the filter as a cheap pre-check and confirm the small set of positives against an exact structure or a durable store. ### Question the requirement The cheapest fix is often to shrink what has to be remembered. Deduplication is frequently only meaningful within a window — the same click reported twice within a minute — in which case the set holds a minute of keys, not a billion. Or the key can be narrowed: storing a 64-bit digest instead of a full identifier halves the key bytes at a small, quantifiable collision risk. Always ask what exact guarantee the business actually needs before engineering for the strictest reading. ## What a strong answer sounds like "Distinct keys times bytes per entry, and bytes per entry is the key plus overhead plus load-factor slack — call it three times the raw key size, so tens of gigabytes for a billion 16-byte keys. That does not fit, so I would hash-partition into buckets small enough to dedupe independently, which costs one extra pass. If the window is bounded I would exploit that first, and if approximate dedup is acceptable a bit-per-element filter is an order of magnitude cheaper." The shape of that answer — arithmetic, then a named alternative, then the price of the alternative — is what distinguishes an engineer who has run out of memory in production from one who has only read about it.

  • Why is hash-partitioning correct for deduplication, and where does it still break?
    Because equal keys hash to the same value, every duplicate pair lands in the same bucket, so deduplicating buckets independently loses nothing. It breaks under skew: if one key or one hash range holds a disproportionate share of the rows, that bucket still exceeds memory. The fixes are subdividing the hot bucket with a second hash, or detecting dominant keys in a cheap pre-pass and handling them separately.
  • A candidate says a billion 16-byte keys need 16 GB. What is wrong with that?
    It counts only the raw key bytes. A lookup structure also stores per-entry bookkeeping and deliberately keeps a share of its slots empty so probes stay short — that headroom is what buys expected-constant lookup. Realistically the total is two to four times the key bytes, so the honest figure is tens of gigabytes. Under-counting here is the difference between a job that fits and one that dies at peak.
  • When would you accept a probabilistic filter instead of an exact set?
    When a false positive — wrongly treating a new key as already seen, and therefore dropping it — is tolerable or recoverable, and false negatives would not be (the filter never produces those). Estimation, sampling and rate-limiting tolerate it; billing and idempotency do not. In the strict case the filter still helps as a cheap pre-check, with only the reported positives confirmed against an exact structure.

saying these in an interview costs you the question

  • Sizes a set as raw key bytes with no overhead
  • Answers only in asymptotics with no byte arithmetic
  • Assumes a lookup structure is packed full
  • Never questions whether dedup needs a global window
  • Treats a probabilistic filter as exact
  • Ignores skew when partitioning by key hash

context