Cruxly › System design › Running the Design Interview
The First Diagram
smallest system that meets the numbers
Open the interactive lessonNow you draw — but you draw the simplest thing that satisfies the requirements and the estimates, and nothing more. Every box must be justifiable by pointing at a number you already wrote down.
Start embarrassingly simple
Your first diagram should be small enough that it feels like you are underselling yourself:
clients → load balancer → stateless app servers → database
That is it. No cache, no queue, no search cluster, no CDN.
This feels risky. It is not — it is the strongest opening available, for two reasons:
- It is correct: it genuinely serves the requirements, just not yet at the required scale.
- Everything you add next is now visibly motivated, because you add it in response to a specific problem you identified in this diagram.
:::key The arithmetic of boxes
- A candidate who draws twelve boxes immediately has to defend twelve decisions they never made.
- A candidate who draws four and grows to twelve has made eight arguments — and the interviewer watched every one of them land.
:::
Grow by breaking it
Attack your own diagram in a fixed order, and only add a box when the attack lands. Watch it grow:
| # | Attack | What you say | What you add |
|---|---|---|---|
| 1 | Traffic | "At 50,000 writes/s a single primary is several times over" | Partition by conversation ID |
| 2 | Storage | "700 TB/year doesn't sit on one volume, and reads are almost all recent" | Tiering to object storage |
| 3 | Latency | "A feed read does eight queries — 80 ms of database time" | A cache, and say what's in it |
| 4 | Failure | "If a shard's primary dies, that conversation is down" | Replication and failover |
| 5 | Spikes | "A push fan-out at 10⁷ is not a synchronous request" | A queue and a worker pool |
:::key The rule that keeps this honest
- Each addition is one sentence of problem, one box of solution. If you cannot state the problem, do not draw the box.
- A cache that isn't answering a latency number is decoration.
:::
Define the contract at each hop
- Boxes and arrows are the least interesting part of a diagram. What earns credit is knowing what crosses the arrow.
- For each connection, be ready with three things:
@@ 1 | Direction and protocol
- Who initiates? Request/response, or does the server push?
:::key The observation worth more than the whole diagram
- For chat you need server push, which means WebSocket or long-poll — which means the app servers are not stateless any more.
- A connection is state. Noticing that unprompted reframes the entire design.
:::
@@ 2 | Payload shape
POST /messages
{ "conversation_id": "c_88", "client_message_id": "uuid-v4", "body": "..." }
→ { "server_id": "m_4471", "timestamp": 1731000000 }
- The
client_message_idis the idempotency key that makes a retry safe.
@@ 3 | Failure behaviour
- What does the caller do on timeout? Retry (needs idempotency), fail fast (needs a user-visible error), or queue (needs durability)?
- This third one is where seniority shows. Most candidates design the happy path and treat failure as an afterthought.
Stateless is the property that buys scaling
- "Horizontally scalable" means any request can be served by any instance, so adding instances adds capacity.
- That only holds if the instance holds nothing the next request needs.
| State | Where it must live | If you get it wrong |
|---|---|---|
| Session / auth | A signed token the client carries, or a shared cache | Instance memory means a user logs out when a box restarts |
| Uploads in progress | Object storage keyed by upload ID | Local disk means a failed instance loses the upload |
| Rate-limit counters | A shared store | Ten instances × local limit of 100 = an actual limit of 1,000 |
| Long-lived connections | Can't be externalised — see below | — |
:::key Long-lived connections: the crux of every real-time design
- A WebSocket is pinned to one server. You cannot make it stateless.
- So you make it discoverable instead: a registry mapping
user_id → server_id, with delivery routed through the registry. - Say this explicitly — it's the piece that separates a real real-time design from a box labelled "WebSocket".
:::
The test to run on every box you draw: if I kill this instance mid-request, what is lost? The answer should be "one request." Anything more is state you haven't externalised yet.
What to leave out
Do not draw: authentication (say "assume an auth service issues tokens" and move on), monitoring and logging (name them in one sentence at the end), CI/CD, or an admin panel.
- They are real, they are not what is being assessed, and each one costs you roughly two minutes of deep-dive time you will want back.
:::warn Naming a vendor before naming the property
"I'd use Kafka here."
→ says you know a name
"I need a durable append-only log with consumer groups — Kafka, say."
→ says you know what you need, and that the requirement drove the choice
The ordering is the whole signal. Property first, product as shorthand second. :::
How to say it in the interview
"I'll start with the simplest architecture that meets these numbers and then break it deliberately. That way every piece of complexity I add has a reason you can see."
Checklist
- Drew four boxes or fewer first
- Added each component in response to a stated problem
- Named the payload and protocol on the critical path
- Said what happens on timeout at each hop
- Identified every piece of state and where it lives
- Handled long-lived connections explicitly if the system pushes
- Described components by property before naming a product