A repeatable 45-minute framework, plus three worked designs — chat with presence, ride-hailing, and video streaming — with the trade-offs an interviewer actually probes.
Module O-10 — Capstone: The System Design Interview
What this module covers: What a system design interview actually assesses, which is not whether you know an answer. A repeatable 45-minute framework with time boxes, and the single highest-leverage move in it — stating the non-functional priority out loud in minute four, because every subsequent decision follows from it. A compressed toolkit that maps a problem shape to the mechanism this course built for it. Then three worked designs, each chosen to exercise a different half of the curriculum: chat with presence, where the fan-out answer inverts the one from Module A-14 and for a nameable reason; ride-hailing, where the dominant write load is disposable data and the matching problem is a distributed lock you should design away; and video streaming, where the design is a bandwidth and cost problem rather than a request-rate problem. Each with the trade-offs an interviewer probes. And a closing pass over the five through-lines that have run the length of the course.
What Is Actually Being Assessed
Not recall of a canonical design. The signals an experienced interviewer is reading:
Signal
What it looks like
Scoping
turning an ambiguous prompt into three or four concrete requirements
Arithmetic
doing the numbers, roughly, out loud, and letting them decide the design
Decisions with costs
"sharding, and the cost is cross-shard queries and a resharding project"
Prioritisation
naming what you are optimising for before choosing mechanisms
Communication
narrating reasoning so the interviewer can follow and intervene
Adaptability
changing your design when new information arrives, without defensiveness
And the failure modes, which are more consistent than the successes:
Jumping to a solution in minute one, before the requirements exist.
Naming technologies instead of trade-offs. "Kafka, Cassandra, Redis" is not a design; the question is always what each buys and what it costs.
Never doing the arithmetic, so every decision is untethered — this is the most common and most damaging omission.
Over-designing. Multi-region active-active for a product with 40,000 users is a wrong answer, and confidently proposing it demonstrates the opposite of judgement.
Silence while thinking. The interviewer is assessing reasoning they cannot see.
Defending a choice after being shown a problem with it.
Analogy: a structural engineer asked how they would bridge a river. The answer is not "a suspension bridge" — it is "how wide, what load, what soil, what budget, how long must it last, and is a ferry acceptable?" Then a rough calculation, then a proposal with its costs stated, then a willingness to change span type when told the riverbed is silt. Somebody who names a bridge type in the first sentence has told you they do not know what determines the choice.
The 45-Minute Framework
Time
Phase
Output
0–5
Requirements and scope
3–5 functional requirements, the NFR priority, explicit exclusions
5–10
Estimation
QPS average and peak, read:write ratio, storage/year, bandwidth
10–15
API and data model
a few endpoints; core entities; the ID and partition-key choice
15–30
High-level design
the boxes, then one write path and one read path end to end
30–40
Deep dive
the bottleneck — theirs or one you offer
40–45
Failure modes and next steps
what breaks, what you would measure, what you deferred
0–5: Scope, and ask what to optimise for. Pick three to five functional requirements and say what you are not building. Then the move that matters most:
State the non-functional priority explicitly. "This is a payment system, so correctness outranks availability: if the two conflict, we fail the request." Or "this is a feed, so availability and latency outrank consistency: a thirty-second-stale feed is fine, a slow one is not."
That single sentence determines the ledger design, the conflict-resolution strategy, the replication mode, the caching aggressiveness and the failure behaviour. Modules P-24 and A-14 were deliberately built as mirror images to demonstrate it. Say it in minute four and the remaining forty minutes are consistent; skip it and you will make contradictory choices and be caught.
5–10: Do the arithmetic, roughly and aloud. DAU, actions per user per day, seconds in a day (~100,000 is close enough to 86,400), peak as 2–5× average. Storage per record times records per day times 365. Round aggressively and state assumptions — precision is not the point; the point is that the numbers should decide things. "1,200 writes per second with 200 followers each is 240,000 timeline writes per second, so pure write-time fan-out needs a plan" is the arithmetic doing its job.
10–15: The two choices that matter in the data model are the ID scheme (time-ordered for locality and pagination — Module P-3) and the partition key (which must align with the access pattern — Module P-6). Everything else can be sketched.
15–30: Walk the paths. Draw the components, then trace one write and one read through them, end to end, naming what happens at each hop. Most of the interview's signal is here, because tracing a path exposes whether the design is coherent.
30–40: Have a bottleneck ready. If the interviewer does not choose, offer the most interesting one — usually the hot key, the fan-out, or the write amplification. Volunteering the hard part is a stronger signal than being led to it.
40–45: Do not skip this. Failure modes, what you would measure (Module O-1's per-mechanism metrics), and what you deliberately deferred. This is where seniority is visible, and candidates routinely run out of time because they over-invested in the diagram.
Meta-rules throughout: narrate; state every trade-off with its cost named; ask before assuming; and start with the simplest design that meets the stated requirement — one region, one database, a cache — escalating only when a number you computed forces it. Reaching for the complex design without a number to justify it is the over-design failure.
The Toolkit: Problem Shape → Mechanism
The interview is largely a recall test over the course. Compressed:
shard the counter, escrow, conflate (P-6, A-9, A-10)
more expensive reads, stranded capacity
Cross-service consistency
outbox + idempotent consumer (A-3, P-16)
eventual consistency, operator queue
Ordering
partition by entity key (P-15)
no global order
"Exactly once"
at-least-once + idempotent receiver (P-16)
dedupe window and storage
Slow dependency
timeout, breaker, fallback (A-6, A-7)
degraded quality; a fallback to test
Overload
bound the queue, shed, prioritise (A-8)
rejected requests
Global users
partition by home region (A-9)
cross-partition operations
Search / semantic
inverted index; embeddings + rerank (P-20, P-21)
a sync problem, and recall to measure
Large payloads
object storage + pre-signed upload + CDN (A-11)
orphan reconciliation
Must not lose it
durable record + lease + bounded retry + DLQ (P-19)
operational surface
Worked Design 1 — Chat With Presence
Requirements. 1:1 and group messages; delivery and read receipts; online presence; message history; push notification when offline. Out of scope: voice/video, moderation, search.
The priority statement:messages must not be lost, and per-conversation ordering must hold; presence may be approximate and slightly stale. Note this leans towards correctness — closer to Module P-24 than to Module A-14 — which is going to change the fan-out answer.
Numbers. 100 M DAU × 40 messages/day = 4 B/day ≈ 46,000 writes/s average, ~150,000/s peak. At ~300 bytes per message that is ~1.2 TB/day of message data, so storage is the volume and it must be partitioned and tiered (Modules P-9, O-8).
Design.
text
The key decision, and it inverts Module A-14. For a feed, the answer was to materialise a per-recipient timeline. For chat, the better answer is a shared per-conversation log plus a per-user read cursor:
Per-recipient inbox (feed model)
Shared conversation log + cursor
A 100-member group message
100 writes
1 write
Read receipts
a separate structure
a cursor per participant — free
Unread count
maintained counter
latest_seq − cursor — derived
History pagination
per-inbox
one ordered log, trivially seekable
Must-not-lose
many copies to keep consistent
one durable record
The reason for the inversion is nameable, which is what an interviewer wants: a feed reads from thousands of authors and a conversation reads from a handful of participants, so the feed's expensive operation is the read (hence materialise it) while chat's expensive operation would be the write (hence do not). And because the requirement is must-not-lose, one durable record with cursors is easier to reason about than N copies.
Delivery semantics. At-least-once, with the client generating the message ID so a retry after an ambiguous failure is deduplicated rather than duplicated (Module P-16) — the idempotency key comes from the client because only the client knows whether this is a retry or a new message. Clients deduplicate on receipt, and per-conversation sequence numbers let a reconnecting client request "everything after 8891" rather than guessing (Module A-10).
Presence. A TTL key per session refreshed by heartbeat, never a boolean set on connect and cleared on disconnect — the socket to a phone in a tunnel stays open for a long time, so only an application heartbeat detects absence. Presence is a set of sessions (phone, laptop, two tabs), and "last seen 2 minutes ago" is more honest than a green dot whose accuracy is bounded by the heartbeat interval (Module A-10).
Deep-dive candidates. The reconnect storm after a gateway deploy, where the expensive part is authentication and state rehydration rather than sockets (Module A-10). The 100,000-member group, which is the celebrity problem again — deliver via pull rather than pushing to a hundred thousand sockets. And end-to-end encryption, which has a clean architectural consequence worth offering: the server cannot index message content, so search moves to the client and server-side features that read messages become impossible.
What the interviewer probes: what ordering you actually guarantee (per conversation, not global); what happens to messages sent while a client was disconnected (the cursor and the replay); whether you claimed exactly-once (you should not have); and how presence degrades.
Worked Design 2 — Ride-Hailing
Requirements. Drivers publish location; riders request a ride; matching; trip lifecycle; fare and payment. Out of scope: pooling, driver onboarding, maps and routing (assume a routing service).
The priority statement:matching must never double-assign a driver — that is a correctness requirement. Location updates are lossy-tolerant: dropping one is invisible. Two different priorities in one system, which is realistic and worth saying explicitly.
Numbers. 1 M concurrently-active drivers publishing location every 4 seconds = 250,000 writes/s, which dwarfs everything else. And the crucial observation:
That dominant write load is disposable data. A location three minutes old has no value. So it does not belong in Postgres — it belongs in an in-memory store with a TTL, where 250,000 writes/s is unremarkable, and none of it needs backing up (Module O-7's tier 3).
Recognising that the biggest number in the system is the cheapest data is the insight this design is really testing.
Geospatial indexing. "Find drivers near this point" cannot be served by a B-tree on latitude and longitude, because a two-dimensional range query has no useful single-column ordering (Module F-13). The standard answer is to discretise space into cells — geohash, S2 or H3 — and index by cell:
text
Cell size is the trade: small cells mean precise results and more cells to query; large cells mean fewer reads and more candidates to filter. PostGIS with a GiST index is a perfectly good geospatial store — it is the 250,000 writes/s that rules it out here, not the query capability, and saying so distinguishes "I know the tool" from "I know when it fits."
Matching, which is a distributed lock you should design away. The naive approach locks a driver record while deciding. Module A-2's whole argument applies: prefer a design where the lock is unnecessary. Two good answers, and offering both is stronger than offering one:
Single writer by construction. Partition matching by geography — one matching worker owns a city or a cell group — so no two matchers ever consider the same driver. This is Module A-2's strongest technique (partition so there is nothing to lock) and Module A-9's home-region model applied to a city.
Compare-and-set on the driver's state, which is Module P-24's guarded transition:
sql
No lock is held across a network call, concurrent matchers resolve deterministically, and the loser simply picks another driver. Use both: partition for throughput and fairness, CAS as the correctness backstop.
Trip state machine (Module P-24's pattern): requested → matched → driver_arriving → in_progress → completed → paid, with cancelled and expired as terminal states, transitions advanced only by guarded update, and an explicit unknown outcome for the ambiguous case where a driver's acceptance may or may not have been recorded before their connection dropped — resolved by a sweeper, exactly as in Module P-24.
Geographic partitioning makes a city a shard, which is convenient: matching is local, latency is local, and it maps onto Module A-9's partitioned active-active model. Cross-city trips are the edge case to name rather than solve on the whiteboard.
Payment is Module P-24 by reference: an idempotency key per fare, a double-entry ledger, an authoritative webhook from the provider, and daily reconciliation. Saying "this is a solved shape, here is the shape" is a good use of thirty seconds.
Surge pricing is a per-cell supply/demand ratio over a short window — approximate, cached, recomputed every few seconds. It is a stream-processing problem (Module P-15), not a transactional one.
Probes: how do you prevent two riders being matched to one driver (the CAS); what happens when a city's matching worker dies (leader election over the partition — Module A-1); why the location data does not go in the primary database; and what you do about a driver who accepts and immediately loses signal.
Worked Design 3 — Video Streaming
Requirements. Upload, transcode, adaptive playback, resume position. Out of scope: live streaming, recommendations, DRM specifics.
The number that reframes the whole design. Do this arithmetic first, because it changes the subject:
10 M concurrent viewers × 5 Mbps average bitrate = 50 Tbps of egress
This is a bandwidth and cost problem, not a request-rate problem. The request rate is modest; the bytes are enormous. Therefore a CDN is not an optimisation, it is the architecture, and egress is the cost model (Module O-8).
A candidate who computes that number and reorients accordingly has demonstrated the thing the question is for. A candidate who designs a sharded metadata database and never mentions bandwidth has missed the problem.
Upload. Pre-signed multipart directly to object storage, so gigabytes never traverse the application tier; constraints inside the signature; a storage event to a queue as the authoritative completion signal, never the client's word; and a lifecycle rule to abort incomplete multipart uploads, which are billed and invisible (Module A-11).
Transcode pipeline. Keep the original (mezzanine) forever and treat every rendition as a cache — the single most consequential decision, because a codec change or a transcode bug then becomes a re-run instead of permanent loss. Split the source into segments, transcode them in parallel on spot capacity with per-chunk checkpointing (Modules A-11, O-8, P-19), then assemble an HLS/DASH manifest per rendition. Adaptive bitrate is why one video is a manifest plus thousands of small objects, and the client switches level between segments based on its own measured bandwidth.
Delivery. CDN in front, always: latency, collapsed origin load, and egress that is materially cheaper than from the bucket. Content-addressed immutable keys get max-age=31536000, immutable, so there is no invalidation problem (Module A-11). Range request support, or every seek is a full origin fetch. And the signing decision that is a rite of passage: signed cookies or a signed path prefix, not per-object URLs, because a player fetches thousands of segments dynamically and rewriting a manifest with thousands of signed URLs that expire mid-playback does not work.
Playback position is an instructive small problem: a high-frequency, low-value, per-user write. Debounce and conflate it — send the latest position every ~10 seconds rather than every second (Module A-10) — and classify it as tier 3, since losing it is a minor annoyance and not worth a backup (Module O-7).
View counts and watch-time are an event stream into a warehouse (Modules P-22, P-23), never a counter incremented on a row — that is the hot-row, relative-update mistake from Modules P-16 and P-24.
Deep dive: cost per stream-hour. 5 Mbps for an hour is ~2.25 GB, so at CDN egress rates a single viewer-hour costs real money and total spend scales linearly with watch time. The levers are the CDN contract, encoding efficiency (a better codec is a direct margin improvement), per-title tiering of originals, and cache hit ratio at the edge. This is Module O-8's argument in its purest form: the dominant cost is set by an architectural decision, and the finance team can only report it.
Probes: why not serve from origin; how ABR actually switches; what the cost per stream-hour is; what breaks at 10× (the answer is the CDN contract and the transcode fleet, not the database); and how you would handle a title that goes viral (pre-warm the edge, exactly as with the celebrity post in Module A-14).
Closing the Course
Three designs, three different halves of the curriculum, and one shared method: state the priority, do the arithmetic, let the numbers choose the mechanism, and name what each choice costs.
The five through-lines this course was built around, restated as the things worth carrying out of it:
Physics → engine → key → product → invoice. Sequential access beats random (F-2); LSM-trees exploit that (P-2); random UUID keys destroy it (P-3); Kafka is the insight sold as a product (P-15); and provisioned IOPS is a line on the bill (O-8). A decision about a primary key is a decision about money.
Data in → data out. Modelling for writes (F-13, F-14) is not modelling for reads: OLAP is a different physical shape (P-22), CDC is how data leaves correctly (P-23), and deletion must reach every copy it landed in (O-9).
Claim → proof. A circuit breaker, a fallback, a retry budget and a failover are hypotheses (A-6, A-7). A load test finds the knee (O-5), fault injection proves the mechanism fires (O-6), and the error budget says whether the promise held (O-2).
One tenant → many. An isolation model (P-8) needs per-tenant limits (P-18), enforcement that no query can bypass (A-13), and a per-tenant cost figure (O-8) — or one customer's behaviour becomes everyone's outage and your margin.
Abstraction → its bill. Serverless is stateless by force (F-10), which is why it exhausts connection pools (P-4) and cannot hold a WebSocket (A-10). Every abstraction removes something, and the something is usually the thing you need at scale.
And the actual point, which is not a set of memorised designs:
Every architectural decision is a trade. The skill is naming what you are buying, naming what it costs, and being able to say which number made you choose.
A design you can defend with arithmetic and whose costs you stated yourself is a good answer even when a different design would also have worked. A design you cannot justify is a bad answer even when it happens to be the one the interviewer had in mind.
Summary
The interview assesses scoping, arithmetic, decisions with named costs, prioritisation, communication and adaptability — not recall of a canonical design. The consistent failure modes are jumping to a solution, naming technologies instead of trade-offs, never doing the numbers, over-designing for a scale you were not given, thinking in silence, and defending a choice after being shown its flaw.
Use the time boxes: 5 minutes on requirements and scope, 5 on estimation, 5 on API and data model, 15 on the high-level design tracing one write and one read path end to end, 10 on a deep dive, 5 on failure modes and next steps — and do not skip the last box, which is where seniority shows.
The highest-leverage move is stating the non-functional priority out loud in minute four. "Correctness over availability" (a payment system) versus "availability and latency over consistency" (a feed) determines the ledger design, the conflict strategy, the replication mode and the failure behaviour. Modules P-24 and A-14 are deliberate mirror images of each other to demonstrate it.
Two data-model choices carry the design: a time-ordered ID for locality and pagination, and a partition key aligned with the access pattern. Start with the simplest design that meets the stated requirement and escalate only when a number you computed forces it.
Chat: use a shared per-conversation log plus a per-user read cursor, which inverts the feed's materialised timeline — and the reason is nameable: a feed reads from thousands of authors so its read is expensive, while a conversation reads from a handful so its write would be. Read receipts and unread counts then become a cursor and a subtraction. The client generates the message ID, because only the client knows whether a send is a retry. Presence is a TTL lease per session with honest "last seen," never a boolean cleared on disconnect. And end-to-end encryption means the server cannot index content, so search moves to the client.
Ride-hailing: the dominant write load — 250,000 location updates per second — is disposable data, so it belongs in an in-memory store with a TTL and needs no backup. Recognising that the largest number is the cheapest data is the insight being tested. Index space by discretised cells (geohash/S2/H3) rather than a two-dimensional B-tree; PostGIS is a fine geospatial store and it is the write rate that rules it out.
Matching is a distributed lock you should design away: partition matching by geography so a single writer owns each area, and use a compare-and-set on driver state as the correctness backstop, so nothing is held across a network call and concurrent matchers resolve deterministically. The trip is a guarded state machine with an explicit unknown outcome and a sweeper, exactly as in the payments capstone.
Video streaming is a bandwidth and cost problem: 10 M concurrent viewers at 5 Mbps is 50 Tbps of egress, which makes the CDN the architecture rather than an optimisation and makes egress the cost model. Pre-signed multipart uploads keep bytes out of the application tier; keep the mezzanine forever and treat renditions as a cache; transcode segment-parallel on spot with checkpointing; serve immutable content-addressed keys with year-long TTLs; support Range; and sign with cookies or a path prefix, because a player fetches thousands of segments. Playback position is conflated, debounced tier-3 data; view counts are an event stream to a warehouse, never a counter on a row.
The five through-lines to carry out of the course: physics becomes an invoice; writing shapes are not reading shapes; every resilience claim is a hypothesis until injected; multi-tenancy needs isolation, limits, unbypassable enforcement and per-tenant cost; and every abstraction removes something you will eventually need.
And the point: every architectural decision is a trade, and the skill is naming what you are buying, naming what it costs, and being able to say which number made you choose. A design defended with arithmetic and honest costs is a good answer even if another design would also have worked.
That is the end of System Design In-Depth — 64 modules from the memory hierarchy to a multi-region ledger. The material was never really about the designs; it was about the habit of asking what a choice costs, and being able to answer.
Knowledge Check
A candidate is asked to design a group chat system. In the first two minutes they sketch a per-recipient inbox with fan-out on write, citing a well-known feed architecture. The interviewer asks about a 50,000-member group. What has gone wrong, and what is the correct reasoning?
In a ride-hailing design, a candidate proposes storing driver location updates in the primary Postgres database with a PostGIS GiST index, and matching riders to drivers by taking a row lock on the chosen driver. What are the two objections, and what does the module propose instead?
Asked to design a video streaming service, a candidate spends thirty minutes on metadata sharding, a search index and a recommendation pipeline, and mentions a CDN once in passing. What does the module say they missed, and what should have reoriented the design?
Test your knowledge with more question sets
Sign in to access a wider variety of questions and get notified when new practice sets are added to this module.
clients ──WebSocket──► gateway tier (stateful, holds sockets — A-10)
│ session registry: client → gateway (Redis, TTL)
▼
message service
│ assigns per-conversation sequence number (A-1)
├─► durable store, partitioned by conversation_id (P-6)
└─► Kafka, keyed by conversation_id (P-15) → ordering per conversation
│
fan-out/delivery workers ──► gateways holding recipients' sockets
└─► push service for offline recipients
driver location write: SET cell:{h3_index} ← add driver, with TTL
rider nearby query: read the rider's cell + its 6 neighbours, then filter by exact distance
UPDATE drivers SET state ='assigned', trip_id = $2WHERE id = $1AND state ='available';-- zero rows = someone else got them