Step 21 of 24
System design interviews
Run a 45-minute system design interview: budget your time, drive the conversation, go deep where it matters, and show the signals interviewers score at each level.
The system design round is where mid-level and senior candidates are separated. There's no single right answer, no compiler, and the interviewer says very little — which makes it unnerving if you've only ever designed systems at work, slowly, with a team. This lesson isn't about the building blocks themselves; the System Design roadmap teaches those, and How to approach a design gives the order of work. This lesson is about the interview: what's being scored, how to spend 45 minutes, how to drive the conversation, and a condensed walkthrough of a classic prompt.
What interviewers are scoring
The prompt is deliberately vague ("design a ride-hailing backend") because the first thing being assessed is whether you can turn ambiguity into a plan. Typical rubric areas:
| Area | Strong signal |
|---|---|
| Requirements | Asks focused questions, separates must-haves from nice-to-haves, states assumptions |
| Estimation | Uses rough numbers to drive decisions, not as a ritual |
| High-level design | A simple, complete architecture that satisfies the requirements end to end |
| Deep dives | Goes deep on the hardest parts; knows data models, failure modes and alternatives |
| Trade-offs | Names what each choice gives up, and why it's acceptable here |
| Communication | Drives the session, structures the whiteboard, adapts to the interviewer's steering |
Expectations rise with level. A mid-level engineer should produce a sound, working design and handle the interviewer's follow-up questions. A senior engineer is expected to drive the whole session, identify the hard problems unprompted, go deep on two or three of them, and discuss operational concerns — monitoring, failure handling, migration, cost. A staff candidate adds organisational and evolutionary thinking: how teams own the pieces, how to roll it out, what changes at 10× scale.
A 45-minute time budget
The biggest failure mode is spending 25 minutes on requirements and boxes and never reaching a deep dive. Keep a clock visible and roughly follow:
| Minutes | Phase | Output on the board |
|---|---|---|
| 0–5 | Requirements | 3–5 functional requirements, key non-functional ones, out-of-scope list |
| 5–10 | Estimation | QPS (read and write), storage, any number that changes the design |
| 10–15 | API and data model | Main endpoints or events; core entities and their access patterns |
| 15–25 | High-level design | Boxes and arrows covering every requirement end to end |
| 25–40 | Deep dives | 2–3 hardest parts: data model at scale, consistency, hot spots, failures |
| 40–45 | Wrap-up | Bottlenecks, what you'd monitor, what you'd do next |
For a 60-minute round, give the extra time to the deep dives. Say the plan out loud at the start — "I'll spend a few minutes on requirements, do some quick numbers, sketch the API and a high-level design, then go deep on the parts that are hardest" — so the interviewer knows you'll get there, and can redirect you early if they want something else.
Driving the conversation
The interviewer is a collaborator who also has a rubric. Make their job easy:
- Lead. Don't wait to be asked the next question. "Now that we have the high-level flow, I think the hardest part is matching drivers in real time. Shall I go deep on that?"
- Check in at transitions, not every sentence. The interviewer's steering is a signal: if they ask about something, it's on their rubric.
- State assumptions explicitly when information is missing: "I'll assume 10 million daily riders, mostly in a few large cities — tell me if you'd like a different scale."
- Start simple, then evolve. Draw one service and one database first, then scale the parts the numbers say need it. Starting with Kafka, Redis, three microservices and a service mesh signals cargo-culting, not seniority.
- Name trade-offs every time you choose. "I'll use Redis for driver locations because they're ephemeral and updated every few seconds; we lose durability, which is fine since drivers re-send their position."
- Handle pushback gracefully. When the interviewer questions a choice, they may be probing your reasoning, not saying you're wrong. Explain your reasoning, consider the alternative honestly, and change your mind if theirs is better — that's a positive signal.
- Keep the board organised: requirements top-left, numbers next to them, API and data model to one side, the architecture in the middle. A messy board makes your design look messier than it is.
Walkthrough: "Design the backend for matching riders with nearby drivers"
A condensed version of how a strong answer flows.
Requirements (5 min). Riders request a ride from their location; the system finds a nearby available driver and offers them the trip; the driver accepts or declines within 10 seconds; both see each other's location until pickup. Out of scope: payments, pricing, ratings. Non-functional: match within a few seconds; high availability for ride requests; driver locations can be slightly stale (a few seconds) — that's an explicit consistency trade-off.
Numbers (5 min). Say 1 million active drivers at peak, each sending a location every 4 seconds: 250,000 location writes/second — the dominant load, and it shapes the design. Ride requests: 10 million rides a day ≈ 120/s average, maybe 1,000/s at peak. So: write-heavy on locations, modest on requests. Each location update is about 50 bytes; the live set is 1 M × 50 B = 50 MB — it fits in memory easily.
API (3 min). PUT /drivers/{id}/location (or a persistent connection — mention it), POST /rides returns a ride ID, POST /rides/{id}/accept from the driver, and a push channel (WebSocket or mobile push) to deliver offers and status.
High-level design (10 min). A location service receives driver pings and writes the latest position into an in-memory geospatial index. A ride service creates ride requests in a database. A matching service queries the index for nearby available drivers, ranks them, and offers the ride to one driver at a time through a notification gateway that holds driver connections. Ride state transitions are stored durably in PostgreSQL.
Deep dive 1: the geospatial index (8 min). Querying "drivers within 2 km" against 1 million moving points needs a spatial index. Options: geohash or S2/H3 cells — split the map into cells, key drivers by cell ID, and search the rider's cell plus neighbours; or a Redis geo set (GEOADD/GEOSEARCH), which uses geohash internally. Partition by city or cell range so one node doesn't take all 250,000 writes per second. Trade-off: cell size — too large and you scan too many drivers; too small and you search many cells. See the geospatial indexes note.
Deep dive 2: double-assignment (5 min). Two matching workers might offer the same driver two rides at once. Make "reserve driver for ride" an atomic conditional write — SET driver:42:lock ride:9 NX EX 15 in Redis, or UPDATE drivers SET current_ride = $1 WHERE id = $2 AND current_ride IS NULL — so only one offer wins, and the lease expires if the driver doesn't respond. Mention that ride state changes should be idempotent, since mobile clients retry.
Deep dive 3: failures (2 min). If a location node fails, drivers re-send within 4 seconds, so rebuilding from scratch is acceptable — no replication needed for that data. The ride database needs replication and failover because rides are durable business records.
Wrap-up (5 min). Bottlenecks to watch: location write throughput per partition, hot cells (airports, stadiums — split cells further or add replicas), and the notification gateway's open connections. Monitor match latency, offer acceptance rate and time-to-pickup. Next steps if there were time: batching matches across many riders for global efficiency instead of greedy nearest-driver.
Notice what makes this strong: numbers drove the decisions (250 k writes/s → in-memory index, partitioned; 50 MB → fits in RAM), the hard parts were chosen by the candidate, and every choice came with its trade-off.
Common mistakes
- Jumping to boxes before requirements and numbers.
- Buzzword architecture — adding Kafka, microservices or Kubernetes without a requirement that needs them. The when not to add infrastructure note is a good antidote.
- Breadth without depth — twelve boxes, none explained. Interviewers would rather see three components understood deeply.
- Ignoring the data model. Many designs live or die on the schema, keys and access patterns.
- Never mentioning failure. What happens when this node, this region, this dependency is down?
- Monologuing without checking whether the interviewer wants to go somewhere else.
- Estimation theatre — computing numbers and then never using them.
How to prepare
- Learn the building blocks — work through the embedded System Design roadmap: caching, replication, partitioning, consistency, queues, rate limiting and failure handling.
- Practise the classics end to end, timed, out loud: URL shortener, news feed, chat, rate limiter, notification system, ride matching, food-delivery order tracking, payments, a web crawler, a key-value store.
- Draw on your real work. Interviewers love "at my company we hit this exact problem, and here's what we learned". Prepare two or three systems you've built that you can explain at depth, including what you'd do differently.
- Do mock interviews with someone who plays an engaged interviewer, and ask for feedback on structure and time management, not just the design.
- Read engineering blogs from companies at scale for real-world trade-offs — but always ask what requirement drove each choice.
Practice
You're asked: "Design a notification service that other teams' services call to send push, SMS and email notifications to users — 50 million notifications per day." Write the board you'd aim to have after 15 minutes: requirements (functional, non-functional, out of scope), key numbers, the API, and the high-level components. Then name the two deep dives you'd propose.
Show solution
Functional: services send a notification to a user on one or more channels; templates with variables; respect user preferences and opt-outs; retry failed deliveries; query delivery status. Non-functional: at-least-once delivery with deduplication (no double SMS); p99 enqueue latency under 100 ms for callers; high availability for accepting requests; delivery within seconds for transactional messages, minutes acceptable for marketing. Out of scope: template authoring UI, analytics dashboards, in-app inbox.
Numbers: 50 M/day ≈ 580/s average; assume 10× peaks for campaigns → ~6,000/s. Each record ~1 KB → 50 GB/day of logs; keep 30 days ≈ 1.5 TB. Third-party providers have their own rate limits — likely the real bottleneck.
API: POST /notifications with {user_id, template_id, variables, channels, priority, idempotency_key} → 202 Accepted {notification_id}; GET /notifications/{id} for status.
Components: API service (validates, checks the idempotency key, writes a record, enqueues) → a durable queue split by priority (transactional vs bulk) → channel workers (push, SMS, email) that render templates, check preferences, call providers via adapters, and record results → a status store. A scheduler retries failures with exponential backoff, and a dead-letter queue catches messages that keep failing.
Deep dives to propose:
- Exactly-once-ish delivery: idempotency keys at the API, plus idempotent workers recording "sent" per notification and channel before acknowledging, so redelivered queue messages don't send twice. See delivery semantics and idempotent consumers.
- Provider rate limits and priorities: per-provider token buckets so campaigns don't exhaust limits needed for one-time passwords; separate queues so bulk traffic can't delay transactional messages; failover to a secondary SMS provider. See rate limiting.
Key takeaways
- System design interviews score requirements, estimation, design, depth, trade-offs and communication; expectations rise with level.
- Budget time explicitly — roughly 5/5/5/10/15/5 minutes for 45 minutes — and announce the plan.
- Drive the session: lead to the next step, state assumptions, start simple, name every trade-off, and treat pushback as a discussion.
- Use numbers to make decisions, choose the hard parts yourself, and go deep on two or three.
- Avoid buzzword architectures, breadth without depth, and ignoring failure modes.
- Prepare with the building blocks, timed classics out loud, your own real systems, and mock interviews.
Check yourself
Quiz
0/4 correct · 4 left
1.Twenty-five minutes into a 45-minute interview you are still refining requirements and high-level boxes. What should you do?
2.In the ride-matching walkthrough, what did the estimate of 250,000 location writes per second and a 50 MB live data set justify?
3.The interviewer asks, 'Why did you choose Redis here instead of PostgreSQL?' What is the best interpretation and response?
4.Which is the clearest sign of a weak system design answer?
Concepts in this lesson
One-page notes on each idea, for review or a quick lookup later.
- When not to add infrastructureThe cheapest, most reliable component is the one you never run.
- Idempotency keysMake a retried POST do its work exactly once by remembering the client’s request key.
- Delivery guaranteesAt-most-once, at-least-once and "exactly-once" — and why the last one needs your help.
- Idempotent consumersProcess a redelivered message twice and still get the effect once.
- Geohashes and quadtreesTurn latitude and longitude into keys you can index, so "nearby" becomes a range scan.
Finished reading?
Mark it complete to fill in your roadmap.