Track C — Distributed Systems Design

The reported technical screen's second round was a job scheduler with fault tolerance (../../research/source-report.md row 8), reportedly in Excalidraw. The reported anti-pattern is name-dropping technologies without being able to defend the tradeoff.

This track produces written design artifacts, not reading notes. Twelve of them, each attacked in writing by a hostile staff-level interviewer, then revised.

→ Study guide: WARMUP.md — every primitive from zero: quorums, Raft, leases and fencing, consistency models, partitioning, delivery semantics, load control. d01-job-scheduler.md — a complete worked design with a hostile critique and the revision.


Table of Contents


The Design Template

Use it every time, in this order. It is what keeps you from rambling when the clock is running, and it makes the 45-minute budget survivable.

# Design: <name>

## 1. Requirements and scope
Functional. Non-functional. Explicitly out of scope.

## 2. Scale numbers
The numbers I assumed and the arithmetic I did with them.

## 3. API surface
The three to five calls that matter, with request/response shapes.

## 4. Data model
Tables/collections, keys, indexes — and WHY those keys.

## 5. High-level architecture
Components and flow. This is the diagram.

## 6. Deep dive: the two hardest components
Not the easy ones. The two where the design could actually fail.

## 7. Failure and recovery
For each: DETECTION, CONTAINMENT, RECOVERY. All three legs.

## 8. Bottlenecks and evolution
What breaks first at 10x. What I would change.

## 9. Tradeoffs I explicitly rejected, and why

Section 9 is the one that separates candidates. It is also the one that gets skipped when time runs short, which is why the time budget puts it at minute 35, not minute 44.

Time budget inside 45 minutes: 5 clarify · 5 API and data · 10 architecture and diagram · 15 deep dive · 10 failure and tradeoffs. If you are still drawing boxes at minute 25, the round is lost regardless of how good the boxes are.


Concept Inventory

Each primitive gets a one-page design note in designs/ and appears as a required element in at least one exercise.

C1. Replication and Consensus

PrimitiveThe question it answersWhere it is drilled
Leader electionWho decides, when several replicas could?d01, d05
Raft: log, terms, commit indexHow do replicas agree on an ordered log?d02, d11
Paxos vs Raft, at a usable depthWhy does anyone still mention Paxos?d11
Leases and their expiryHow do you hand out temporary authority safely?d01, d04
Fencing tokensHow do you survive the zombie that comes back?d01, d02
Quorum reads/writes, R + W > NWhat does a quorum actually buy you?d02, d11
Sync vs async vs semi-sync replicationWhat do you lose on failover?d02, d09
Read replicas and replica lagWhy did the user not see their own write?d09, d12

Fencing tokens are the single highest-value item in this list. A lease expires; the holder does not necessarily know. Without a monotonically increasing token that the storage layer checks, a partitioned worker's late write silently corrupts state after its replacement has already run. Naming it unprompted is a reliable staff-level signal, and almost nobody does.

C2. Partitioning and Placement

PrimitiveThe questionDrilled in
Hash vs range partitioningWhich one and what does it cost you?d02, d06
Consistent hashing, virtual nodesHow much moves when a node joins?d02, d12
Rebalancing without downtimeWhat happens to in-flight requests?d02
Hot partitionsOne key is 40% of traffic. Now what?d03, d06
Shard ownership and membership changeWho owned this key during the transition?d01, d02

C3. Storage and Consistency

PrimitiveThe questionDrilled in
Write-ahead loggingWhat survives a crash mid-write?d02, d07
LSM trees vs B-treesWrite-heavy or read-heavy?d07, d08
MVCC and snapshot isolationHow do readers avoid blocking writers?d02
Write skewThe anomaly snapshot isolation still permitsd02
Linearizability vs serializabilityTwo different words for two different thingsd02, d11
The outbox patternHow do you write to a DB and a queue atomically?d04, d10
Idempotency keysHow does at-least-once become tolerable?d04, d10

C4. Messaging and Delivery

PrimitiveThe questionDrilled in
At-most / at-least / "exactly" onceWhy the third one is a lie about deliveryd04, d10
Visibility timeoutsThe queue's version of a leased01, d04
Dead-letter queues and redriveWhere does a poison message go?d04
Ordering guarantees, per-key orderingGlobal ordering costs a single writerd04, d10
Consumer groups and rebalancingWho is reading this partition right now?d10
Backpressure vs buffering vs sheddingThree different answers to "too much"d03, d05

C5. Control Under Load

PrimitiveThe questionDrilled in
Little's law: L = λWThe one equation you must have coldcalculators
The utilization/latency kneeWhy 80% utilization is not "80% as bad as 100%"calculators, d05
Retry storms and retry budgetsHow retries turn a blip into an outaged04, d05
Backoff with jitterFull vs equal vs decorrelatedd04
Circuit breakersHalf-open, and why the threshold is hardd04, d05
Load shedding and admission controlRefusing work as a featured05, and Track D
Cascading failureHow one slow dependency takes down everythingd05
Bulkheads and cellular architectureContaining blast radius by constructiond05, d12

C6. Time

PrimitiveThe questionDrilled in
Wall clock vs monotonicWhy leases must use elapsed time on one noded01
Clock skew and NTP boundsWhat "synchronized" actually meansd01, d11
Logical clocks, vector clocksOrdering without agreeing on timed11
Hybrid logical clocksCausality with a bounded relation to real timed11
CRDTsWhen you can avoid coordination entirelyd11

The Failure-Mode Catalog

Every design you produce must include a failure section, and every failure needs all three legs. "It retries" is not a failure analysis.

FailureDetectionContainmentRecovery
Node crash (fail-stop)Heartbeat / lease expiryTraffic drains to healthy nodesReplacement joins, state re-replicates
Node hang (fail-slow)Latency percentiles, not liveness pingsEject on latency SLO breach, not on ping failureRestart; investigate. Worse than a crash — it answers pings
Network partitionQuorum loss on the minority sideMinority refuses writesMerge on heal; reconcile
Zombie holderYou cannot detect itFencing token rejected at the storage layerNothing to recover if fencing worked
Thundering herd after outageQueue-depth spikeRate-limited catch-up, jittered restartsDrain at a bounded rate
Retry stormRequest rate rising while success rate fallsRetry budget as a fraction of base trafficCircuit break, then half-open probe
Poison messageAttempt count exceededDead-letter after NManual or automated redrive
Hot partitionPer-key metricsSplit, or cache, or rate-limit that keyRebalance
Cascading failureCorrelated latency across servicesBulkheads, timeouts everywhere, sheddingShed until stable, then ramp
Data corruptionChecksums, invariant auditsQuarantine; stop replicating itRestore from a known-good point
Clock skewSkew monitoring against NTPTreat a skewed node as unhealthyResync; re-elect if it was leader

Fail-slow deserves its own emphasis. A crashed node is easy: it stops answering. A node that answers health checks in 2ms while serving real requests in 40s is invisible to liveness probes and poisons every load balancer pointing at it. If your design's only health signal is "does it respond," you have not handled the common case.


Back-of-Envelope Calculators

cd tracks/systems-design/calculators
python3 envelope.py --help
python3 envelope.py qps --rps 50000 --ms 8          # concurrency and cores
python3 envelope.py queue --rho 0.8                  # the utilization knee
python3 envelope.py storage --rows 1e10 --bytes 512  # bytes, replicated
python3 envelope.py retry --rps 10000 --fail 0.3     # retry amplification

The point is not the tool. The point is that you do the arithmetic out loud in the round. "50k rps at 8ms means 400 concurrent in flight, so at 200 per box that's 2 boxes plus headroom — call it 4 for failure tolerance" is worth more than a paragraph of adjectives. Any arithmetic beats none.

Numbers to have memorized cold — verify them yourself with envelope.py latencies:

OperationOrder of magnitude
L1 cache reference~1 ns
Main memory reference~100 ns
SSD random read~16–100 µs
Round trip in the same datacenter~0.5 ms
Disk seek (spinning)~10 ms
Round trip US cross-country~40–70 ms
Round trip US to Europe~80–150 ms

The Twelve Design Exercises

Written into designs/ as artifacts, in this order.

#ExerciseWhy it is here
d01Fault-tolerant distributed job schedulerRow 8 — the reported screen question. Do this one first
d02Distributed versioned KV storeThe distributed counterpart to the reported coding question. Doing both makes the connection between rounds
d03Distributed rate limiterSmall surface, deep tradeoffs. Good early confidence
d04Webhook delivery systemFeeds ../../projects/ — build it after you have designed it
d05Load shedding and admission control gatewayThe reliability primitive everything else leans on
d06Feature store (online + offline)Your background. Should be your fastest
d07Log analytics pipelineIngest, index, query at volume
d08Multi-region metadata storeWhere consistency stops being free
d09Search / retrieval servingYour strongest area — make it the portfolio-adjacent one
d10Event streaming platformConsumer groups, ordering, replay
d11Distributed lock / coordination serviceRaft in anger; fencing tokens; the honest limits
d12Multi-tenant control planeIsolation, fairness, noisy neighbours

d01 and d02 are mandatory and come first. The rest are ordered by leverage, not by difficulty.


The Critique Loop

The mechanism that makes this track work. Every design goes through it.

  1. You write the design against the template, to a 45-minute clock.
  2. I attack it in writing, playing a hostile staff-level interviewer. Not "have you considered" — specific, adversarial, with a concrete failure scenario:

    "Your scheduler claims at-least-once. Worker A claims job J with a 30-second lease, then GC-pauses for 45 seconds. You re-dispatch to worker B. B completes and writes the result. A wakes up, finishes, and writes its result too. Walk me through what the user sees, and tell me which line of your design prevents it. If none does, say so."

  3. You revise, in place, with a changelog at the bottom.
  4. I re-attack the revision, harder.
  5. It is done when I cannot find a failure you have not named — including the ones you name and choose to accept.

Deliberately accepting a failure mode with a stated reason is a staff behavior. Claiming to have handled everything is a junior one, and it is trivially falsified by one good question.


Drill Set

DrillCadenceTrains
Full design, 45 min1–2/weekThe round itself. Template, timer, Excalidraw
Critique + reviseAfter every designDefending under attack
Envelope-only, 10 min3×/weekNumbers reflex. One prompt, arithmetic only, no architecture
Deep-dive selection, 5 minDailyRead a prompt, name the two hardest components in 60 seconds. This is the highest-weight rubric line and it is trainable on its own
Failure-firstWeeklyWrite section 7 before section 5. Forces detection/containment/recovery to shape the architecture
Rejected-alternativesWeeklyTake a finished design and add two more rejected alternatives with quantified reasons
Diagram speedWeeklyRedraw a past design in Excalidraw in 6 minutes

Failure Modes

FailureSymptomFix
Deep-diving the wrong componentsPolished API design, nothing on consensus or leasesDeep-dive-selection drill, daily
No numbersAdjectives instead of arithmeticEnvelope-only drill
Name-dropping"I'd use Kafka" with no defence of the alternativeSection 9 is mandatory
Failure section is one leg"It retries"The three-leg table above
Wrong altitudeClass diagrams, or "we'll use a queue" with no visibility timeoutTime budget enforces the middle
Running out of timeSections 7–9 missingFailure-first drill; enforce the minute markers
Ignoring fail-slowOnly liveness checksRead the catalog; add latency-based ejection
Silent scope decisionsNever stated what is out of scopeSection 1, always

Self-Assessment Rubric

LevelStandard
L0≤8/25 on the diagnostic rubric; no deep dive; no numbers
L19–14; coherent architecture; deep-dives the easy parts; thin failure analysis
L215–20; identifies both hard components; three-legged failures; some quantified rejections
L321–25; both hard components plus fencing/quorum reasoning unprompted; names an accepted failure mode and why; rejections quantified and reversible-condition stated

Scoring detail in ../../diagnostics/RUBRIC.md. The hard cap applies: if you deep-dived the wrong components, the round caps at L1 no matter how good the rest was.


References

  • Kleppmann, M. Designing Data-Intensive Applications, 2nd ed. — Ch. 5 (replication), 6 (partitioning), 7 (transactions, write skew), 8 (unreliable clocks, fencing), 9 (consistency and consensus)
  • Ongaro, D. and Ousterhout, J. In Search of an Understandable Consensus Algorithm (Raft). USENIX ATC 2014. https://raft.github.io/raft.pdf
  • Lamport, L. Paxos Made Simple. 2001.
  • Burrows, M. The Chubby Lock Service for Loosely-Coupled Distributed Systems. OSDI 2006.
  • Corbett et al. Spanner: Google's Globally-Distributed Database. OSDI 2012.
  • Kingsbury, K. Jepsen analyses. https://jepsen.io/analyses — the best available catalog of how real systems break
  • Beyer et al. Site Reliability Engineering. O'Reilly, 2016 — Ch. 21 (handling overload), Ch. 22 (cascading failures)
  • Brooker, M. Exponential Backoff and Jitter. https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/
  • Brooker, M. Timeouts, retries, and backoff with jitter. Amazon Builders' Library.
  • Kulkarni et al. Logical Physical Clocks (HLC). OPODIS 2014.
  • Shapiro et al. Conflict-free Replicated Data Types. SSS 2011.