Skip to content
About
Main content

Scenarios

Longer problems, like those in senior system design interviews. A strong answer estimates with numbers first, names the mechanism, then recommends a fix and says what it costs.

Try each one before you open the answer.
1Design the index for a table of 5 billion event rows of 200 bytes each: 90% of operations are writes by key, 10% are point reads, and there are a few range scans by time.Show the answer

Estimate

first. B+tree: a 16 KB page per 200-byte row ≈ 80×, plus the log and the 's own (about 240× at the flash with a 3× factor). Leveled LSM: ≈ 30; tiered about 3–4, but with up to 30 runs per read.

Choose

With 90% writes the LSM wins. , with more bits on the smaller levels, keep point reads near one I/O.

The range scans

They are the LSM's weak spot, merging across all runs. Make time part of the sort key, or add a time-partitioned layer so a scan touches few runs. Use a on the .

Failure modes

stalls hurt , so pace compaction; watch for key skew.

If rows were large

With 8 KB rows, choose a B-tree, or separate values from keys.

Read moreB-trees: where the bill arrivesLSM-treesFilter allocation

2A nightly hash aggregation took 6 minutes; when the data doubled, it took 70. Diagnose.Show the answer

Diagnose

Twice the data taking about 12 times as long is a cliff, not linear growth: the hash table stopped fitting in memory and the operator started spilling to disk.

Check

Memory per distinct key and the number of partitions, then skew: one hot key gives one task most of the work.

Fix

Reduce state per key, pre-aggregate before the shuffle, salt hot keys, or raise memory or the partition count so each partition fits. Or choose a sort-based plan, whose cost degrades smoothly instead of falling off a cliff.

Trade-off

Sort-based aggregation has a lower peak speed.

Read moreWhere hash tables biteSort-merge join (Query processing)

3Choose a primary key for a table with a very high insert rate in a B-tree database.Show the answer

Random keys (UUIDv4)

Pages end up about 69% full (about 45% space overhead), and every insert writes a random page, inflating the working set.

Time-ordered keys (UUIDv7, ULID)

Inserts append at the right edge and pages fill completely, but the last page and its become a hot spot that limits concurrency.

Recommend

Time-ordered keys for write-heavy tables. If concurrent inserts contend on the hot page, prefix the key with a small shard number so there are several right edges.

Say the trade

You have converted fragmentation into .

Read moreKey order relocates cost

4You join events to a contracts table where each contract is valid from date A to date B. The query plan shows a cross product. Fix it.Show the answer

Diagnose

This is an interval-overlap join, which a one-dimensional sorted structure cannot answer directly, so the engine falls back to comparing everything with everything.

Fix

Divide time into fixed bins and copy each contract into every bin it touches; each is copied about (mean length ÷ bin width) + 1 times. With 10-day contracts and 5-day bins, about 3 copies. Then equi-join on the bin and filter for exact overlap.

Tune

Narrow bins mean more copies; wide bins mean more false candidates to filter. Also cluster the events table by time, so file pruning works.

Read moreInterval treesThe distributed form is always binning

5A dashboard query takes the top 10 depots by delay. It runs for 40 minutes and spills 800 GB. Fix it, and explain why your fix works.Show the answer

Diagnose

The engine fully sorted the input, O(n log n) with memory proportional to the input, hence the , when the question needs only 10 items.

Fix

A of 10: O(n log 10) time, O(10) memory, and an exact answer. If the engine cannot plan that, aggregate by depot first (there are few depots), then take the top 10.

The rule

Before reaching for approximation, restrict the question.

Read moreTop-N

6A distributed sort of 1 TB has 199 tasks that finish quickly and one that runs for hours. Why, and what do you change?Show the answer

Diagnose

A stage takes as long as its slowest task. The range boundaries came from a poor sample, so one range received about ten times its share of the data.

Fix

Take a bigger sample, enough to resolve the keys near the boundaries; split ranges that hold a single hot key (sort by a secondary key, or salt it); and measure partition sizes before sorting.

Trade-off

Sampling costs a pass over the data, or a fraction of it.

Read moreDistributed sort is a partitioning problem

7You must sort 1 TB on one machine with 8 GB of memory and an NVMe disk that reads and writes sequentially at about 2 GB/s. Size the job.Show the answer

Estimate

Runs: 1 TB ÷ 8 GB = 128. The merge is bounded by memory ÷ efficient read size, 8 GB ÷ 128 KB, so tens of thousands are possible and only 128 are needed: .

Time

Total I/O is about 4 TB (read and write for run generation, then again for the merge). At 2 GB/s that is about 2,000 s, roughly 33 minutes.

Design

A for the merge, and that compare with a plain memcmp. Do not tune the number of passes; there is only one.

Read moreExternal merge sort (Query processing)Use a loser tree

8Over a high-rate stream, compute a 30-minute sliding max, a sliding sum, and a sliding distinct count. How does each scale?Show the answer

Sum

Invertible: O(1) state.

Max

Not invertible. Use a on one thread, or one-minute in a distributed engine, combining 30 panes per output.

Distinct count

Holistic: an exact answer needs memory proportional to window × cardinality, which is how such jobs run out of memory. The only bounded answer is a , with its error.

Trade-offs

The deque needs ordered, single-threaded access. Panes cost O(window ÷ slide) per output, but parallelize and tolerate out-of-order data.

Read moreMonotonic queues and dequesThe honest comparison: panes

9Count unique visitors per day, per week and per month for 50 dashboards, with 200 million visitors a day. The pipeline's retries duplicate some events. Design it.Show the answer

Estimate

Exact sets cost about 200 million × 16 bytes = 3.2 GB per day per combination of dimensions, multiplied by every combination.

Design

One per (day, dimension combination): 12 KB each, with 0.81% error. Retries are harmless because the sketch is idempotent. Weekly and monthly numbers are merges of the stored daily sketches, exact under union, so the error does not grow with the period.

The rule

Store sketches, not estimates: summing daily estimates would count returning visitors many times.

Risks

Questions like "visitors who saw both A and B" are intersections, where HyperLogLog's error can exceed the answer: pay for , or keep raw data for those queries. And a new dimension cannot be added later without the raw data.

Read moreHyperLogLogStore the sketch, not the estimate

10In a key-value store on NVMe, 40% of lookups are for keys that do not exist, and each read costs 100 µs. Should you add a filter?Show the answer

Estimate

Without a filter, 40% of lookups pay about 100 µs for nothing. A at 10 bits per key (1% false positives) removes 99% of that, saving about 0.4 × 99 µs ≈ 40 µs per lookup on average. Memory: 1.25 GB per billion keys.

Choose the filter

If files are immutable, an XOR or saves about 15% more memory at no real cost, since compaction rebuilds filters anyway. Use a blocked layout so a probe costs one cache miss. If keys are deleted often from mutable files, consider a , accepting that inserts can fail.

When not to

If most lookups found their key, the filter would be pure overhead.

Read moreWhat a filter is actually worthCuckoo filterXOR and ribbon filters

11From a stream of 10 billion events, find the 10 most-viewed products in one pass with 100 MB of memory. The product team wants to show ranks 1 to 10.Show the answer

Design

or with, say, 10,000 counters, a tiny fraction of 100 MB. The error is at most N / k = 10¹⁰ / 10⁴ = 10⁶ events per counter.

What it guarantees

No item with more than a million views can be missed, but the order among the survivors is reliable only where their true counts differ by more than the error.

The ranks

Show the list without exact numbers, or make a second pass with exact counters for just the candidates. Count-Min with a also works, but is larger and probabilistic.

Note

Counting is not idempotent, so deduplicate retried events upstream.

Read moreTop-K and heavy hittersCount-Min Sketch

12Latency dashboards compute p99 per minute for each service. The team wants hourly and daily p99 by averaging the per-minute values. Is that right?Show the answer

Answer

No. Shard A with p99 = 100 ms over 1,000 requests and shard B with p99 = 20 ms over 9,000 have a combined p99 anywhere between 20 and 100 ms; their average means nothing.

Fix

Store a mergeable digest per minute and merge upwards.

Choose the digest

if tail accuracy at p99 or p99.9 matters and an empirical guarantee is acceptable; if you need a provable rank error; fixed-bucket histograms are the cheapest mergeable option, but only as good as their bucket boundaries.

Read moreQuantile sketches

13Detect near-duplicates among 10 million product descriptions. Comparing all pairs is too slow.Show the answer

Estimate

All pairs is about 10¹⁴ comparisons.

Design

A 128-value signature per document (similarity error about 1/√128 ≈ 9%), then banding with, for example, b = 16 bands of r = 8 rows (threshold about 0.71) to produce candidate pairs, then verify each candidate exactly.

State the recall

Pairs with 0.9 similarity are found with probability about 0.9999; pairs at 0.6, much less often. Some borderline duplicates will be missed: choose b and r from the similarity you care about.

Measure

This is the one structure in the family whose error silently changes the output, so measure recall on a labelled sample.

Read moreMinHashSimHash and LSH

14You need a secondary index on a 5-billion-row immutable event table for the columns status (6 values) and depot (2,000 values), and it must fit in 64 GB of RAM.Show the answer

Naive bitmaps

One bitmap per value: 6 × 5·10⁹ bits = 3.75 GB for status, but 2,000 × 5·10⁹ bits = 1.25 TB for depot.

Roaring

Each bitmap is split into chunks of 65,536 rows. Sparse chunks, with fewer than , are stored as 16-bit arrays; dense chunks as 8 KB bitmaps. Space becomes proportional to the number of rows: about 5·10⁹ × 16 bits = 10 GB for depot, while status (dense) stays near 3.75 GB.

Queries

Word-parallel AND and OR, with popcount for counts.

Updates

The table is immutable, so indexes are rebuilt with compaction; new rows go to a delta that the rebuild merges.

Trade-offs

No in-place updates, and build time. Chunks near the 4,096 boundary waste some space.

Read moreBitmap indexes and Roaring

15An ingest service gets less throughput with 32 threads than with 8. Diagnose and fix.Show the answer

Hypothesis

, not computation: by the , falls past a peak number of threads.

Find the hot cache lines

Profile cache-coherence events (for example with perf c2c) and look for a global counter, a shared 's head and tail, or a reader-writer .

Fix, in order of preference

  1. Partition by a hash of the key into single-writer shards, so the index needs no lock.
  2. Hand events to the shards through single-producer, single-consumer rings, with head and tail on separate and batched publication.
  3. Replace the global counter with padded per-thread counters, summed when read.

Numbers

If each cache-line transfer takes 70 ns, a shared counter caps out near 14 million increments per second for the whole machine, however many threads you add.

Trade-offs

Sharding is limited by skew (a hot key pushes one shard to 100%), and queries across shards need a merge. Check by fitting the scalability curve before and after: the coherency term should drop to near zero.

Read moreThe currency is coordinationThree ways not to coordinateRing buffers

16Design the in-memory index for a service with a few writers and thousands of read queries per second, where reports need a consistent view of the index as it was at 02:00.Show the answer

Design

A persistent map (for example a hash array mapped ). Writers build new versions by copying the path to the root, 5 or 6 nodes of about 256 bytes each for a billion entries, and publish with one on the root. Readers load the root once and need no locks and no writes, so read throughput scales with cores. Reports hold on to an old root, which gives the 02:00 snapshot for free.

Costs

For the writer, 2–10× slower and larger than a mutable map, and garbage you must collect.

Reclamation

, or a garbage-collected language. Alert on the age of the oldest active reader: one stalled reader blocks reclamation, and memory grows without bound.

When not to

With a single thread and no need for snapshots, a plain mutable map wins.

Read moreImmutable and persistent structuresSafe reclamation

17A PostgreSQL table keeps growing although it is vacuumed regularly, and an Iceberg table's storage cost grows although the data volume is flat. Find the common cause.Show the answer

The common cause

A blocked by its slowest reader.

PostgreSQL

Vacuum cannot remove row versions newer than the oldest open snapshot, so a long-running or idle-in-transaction session blocks it.

Iceberg

Expiring snapshots keeps every file reachable from a retained snapshot, so a long retention window or a pinned reader keeps old files alive.

Fix

Find the oldest open transaction or snapshot, set statement and idle-in-transaction timeouts, shorten retention to what audit actually needs (for example 168 hours), and alert on the age of the oldest snapshot.

Trade-off

Shorter retention means less time travel and less slack for slow jobs.

Read moreThe same question at every scaleThe immutability law

18A shared work queue has 4 producers and 1 consumer, at 500,000 items per second. Choose between a mutex, a reader-writer lock, a lock-free queue and sharding.Show the answer

Estimate

500,000 items per second is modest. An uncontended lock costs about 20 ns, so even a plain mutex spends about 500,000 × 20 ns = 10 ms per second on the lock, roughly 1% of one core. Four producers add contention, but not enough to reach the knee.

Recommend

A mutex, because it is simpler. Move to a multi-producer , with to claim slots, only if profiling shows time spent waiting for the lock.

If you build the ring

Head and tail on separate cache lines; depth from (500,000 × 20 ms of backlog = 10,000, rounded up to 16,384); a full ring means .

Trade-off

buys predictability under preemption, not throughput. slot claiming is the only option that bounds tail .

Read moreMPSC: the choice of primitiveProgress guarantees

19A concurrent hash map slows down 20 times on one workload, but only when the keys are skewed. What happened, and what are your options?Show the answer

Diagnose

With skew, most operations hit a few bins. (say 16 segments) or per-bin compare-and-swap then concentrates contention on one stripe or one cache line. The design assumed uniform keys. Padding will not help: the problem is the data, not the layout.

Options

  1. Split hot keys across several sub-entries and combine them on read (salting).
  2. Make reads of hot keys write nothing, using immutable or versioned values.
  3. Route hot keys to dedicated single-writer shards.
  4. Use per-core counters for aggregate values.

Test

Fit the scalability curve with uniform and with skewed keys; the contention term should jump for the skewed case.

Read moreThe physical-contention lawConcurrent hash maps

20Design the read path of a store of 5 billion events (25,000 files) so that the query 'all events for tracking ID T last Tuesday' answers in under 50 ms. Where are the cliffs?Show the answer

The cascade

  1. Min/max statistics on the timestamp prune files, if the data is clustered by time. Cliff: unclustered data prunes nothing.
  2. A Bloom filter per file on the tracking ID at 1% false positives. Cliff: filters too large for memory, or mistyped IDs hitting the SSD.
  3. A sparse index with 8,192-row granules narrows to one granule. Cliff: the wrong granule size.
  4. A block scan reads about 8,192 rows.

Numbers

25,000 files; 7 selected by time; about 1.06 expected after the Bloom filters; one granule. About 8,192 rows examined out of 5 billion.

Correctness

Every stage errs only towards "maybe", so no result is lost.

Memory

Keep filters and indexes resident; a lookup then costs one or two SSD page reads.

Read moreThe pruning cascadeSparse index granules

21Explain, with two examples, why the merge policy of an LSM-style write path is the dial between read and write costs. Then recommend a policy for a log of scan events that is read 20 times more than it is written.Show the answer

The dial

The merge policy decides how many a read must consult and how often each byte is rewritten.

Two examples

Leveled merging keeps few runs per level, so reads are cheap but each byte is rewritten tens of times. Tiered merging rewrites less, but reads must check more runs.

Recommend

With reads 20× writes, choose leveled (or a hybrid), with a Bloom filter per run to cut read cost further. Watch the collector: old files can be deleted only when the slowest reader has finished with them.

Failure mode

Merging falls behind, runs pile up, and read latency climbs without warning.

Read moreThe buffer–runs–merge write pathThe amplification arithmetic