Interview section
Cracking the system design round
The round is not a knowledge test. It is a simulation of a design discussion with a colleague, and it is graded on how you reason, communicate and handle pushback. Everything below is aimed at that.
The 45-minute framework
Say the plan out loud at the start — "I'll spend five minutes on requirements, then estimates, then the API and data model, then draw it, then we can go deep wherever you like." It signals structure before you've designed anything, and it gets the interviewer to agree to your agenda.
| Time | Phase | What you must produce |
|---|---|---|
| 0–5 | Requirements | 3–5 functional requirements written down, and the non-functional ones: scale, read/write ratio, latency target, consistency needs. Explicitly say what's out of scope. Get agreement before moving on. |
| 5–10 | Estimation | QPS (average and peak), storage over 5 years, bandwidth. And the implication of each number — an estimate you don't use is wasted time. |
| 10–15 | API | 4–6 endpoints with parameters and return shapes. This pins down what the system actually does and often surfaces a missed requirement. |
| 15–23 | Data model | Entities, chosen store with a reason, shard key with a reason, key indexes. This is the highest-signal section — spend the time. |
| 23–35 | High-level design | Draw the happy path end to end. Client → LB → service → cache → store, plus async paths. Walk one read and one write through it out loud. |
| 35–43 | Deep dive | Let the interviewer choose. If they don't, go to your own weakest box or the bottleneck: "the fan-out is the risky part, let me go there." |
| 43–45 | Wrap | Summarise the design in three sentences, name the top two tradeoffs you made, and say what you'd do next with more time. |
The most common failure is spending 25 minutes on requirements and API and never reaching the interesting part. Wear a watch. At 15 minutes you should be drawing. If you're behind, say "I'm going to move on to keep us on time" — that reads as senior, not rushed.
How you're actually graded
| Dimension | Weak | Strong |
|---|---|---|
| Requirements | Starts drawing immediately; assumes scale | Asks 4–5 targeted questions, states assumptions, bounds scope |
| Estimation | Skips it, or computes numbers and never uses them | Numbers drive decisions: "45 PB, so not in a database" |
| Tradeoffs | Presents choices as facts | Every choice has a named alternative and a stated cost |
| Depth | Only names technologies | Can explain what's inside the box and how it fails |
| Failure handling | Only the happy path | "When this dies, here's what happens and here's the degradation" |
| Communication | Silent thinking, jumps around, ignores hints | Narrates reasoning, structured, picks up on steering |
| Handling pushback | Defensive, or instantly abandons a good idea | Engages: "good point — that fails when X; I'd change Y" |
| Judgement | Distributed everything for 1,000 users | Matches complexity to the actual requirement, names the exit |
Phrases that buy time and score points
- "Let me make sure I understand the scope before I design anything."
- "I'll assume X unless you'd rather I optimise for Y."
- "Is this closer to a read-heavy or write-heavy product?"
- "What's explicitly out of scope? I'll skip auth and payments unless you want them."
- "Let me think about this out loud for a second."
- "There are two ways to do this — let me lay out both and pick."
- "I'll start with the simplest thing that works, then find where it breaks."
- "That number's rough, but the order of magnitude is what matters here."
- "…and the cost of that is ___."
- "I'm trading freshness for latency here, which is fine for a feed and wrong for a balance."
- "This is over-engineered for a million users; I'd only do it past ten."
- "I'd start without this and add it when the metric tells me to."
- "That's a good catch — that breaks when ___. Let me fix it."
- "I haven't worked with that directly. My instinct is ___ — does that match how it behaves?"
- "Let me go back to the requirements; I think I over-built this."
- "I don't know that detail, but here's how I'd find out."
Saying it once, followed by a reasoned guess, is a positive signal — it's what a trustworthy colleague does. Bluffing a detail you don't know is the fastest way to fail the round, because the interviewer almost certainly picked a topic they know deeply. "I don't know, but I'd expect it to behave like X because Y" is close to a full-credit answer.
Red flags interviewers write down
- Designing before agreeing on requirements.
- Not one tradeoff in 45 minutes.
- Arguing with a correct hint instead of engaging.
- Fabricating confident details that are wrong.
- Never mentioning what happens when something fails.
- Buzzword lists with no mechanism behind them.
- Microservices for everything, unprompted.
- Blockchain, ML, or Kubernetes as an answer to a data question.
- Ignoring cost entirely — "we'll just add servers".
- Designing only the read path.
- Silence longer than ~20 seconds.
What each level is expected to produce
| Level | Bar |
|---|---|
| Junior / new grad | A coherent design that works. Knows what a cache, a queue and a load balancer are for. Some estimation. Tradeoffs are a bonus. |
| Mid-level | Drives the structure without prompting. Real estimation used to make decisions. Names tradeoffs. Handles the obvious failure cases. Can go one level deeper into any box they drew. |
| Senior | Identifies the actual hard part and spends time there. Discusses failure modes, degradation and operations unprompted. Pushes back on requirements. Mentions cost, migration, and rollout. Knows what they'd defer. |
| Staff+ | Questions the problem itself. Considers organisational boundaries (who owns which service), evolution over years, migration paths from a system that already exists, and the cost of complexity on the team, not just the servers. |
Question bank — 68 questions with answers
Grouped by topic. Answer each out loud before opening it — recognition is not recall, and the round tests recall under pressure.
Scale & estimation
01How do you convert daily volume to QPS in your head?
A day is 86,400 seconds — round it to 100,000 to make the arithmetic trivial and be slightly conservative. So 1 million per day ≈ 12 per second, and 1 billion per day ≈ 12,000 per second. Then multiply by a peak factor of 2–10 (use 3 unless you know the traffic shape) because traffic is diurnal and spiky. Say both numbers: "roughly 12,000 average, call it 35,000 at peak."
02Why estimate at all if the numbers are made up?
Because the order of magnitude determines the architecture, and it's the only thing that does. 300 writes/second is one Postgres box; 300,000 writes/second is a sharded distributed store. 18 TB fits on a single machine; 45 PB is object storage and a CDN. The digits are irrelevant — the power of ten decides everything, and getting it wrong means designing the wrong system.
03What's a reasonable QPS for a single application server?
5,000–15,000 requests/second for a simple, mostly-I/O-bound service on a modern multi-core box; a few hundred to low thousands if each request does meaningful CPU work or several downstream calls. Quote a range, say what it depends on (payload size, downstream calls, language runtime), and add 50% headroom for failures and deploys. Being precise here isn't the point; showing you know it's a range with drivers is.
04Why p99 rather than average latency?
Averages hide the tail. A service with a 50 ms average can have a 4-second p99, and that p99 is a real experience for 1 in 100 requests — which, for a user making 100 requests in a session, is most sessions. Worse, tail latency amplifies under fan-out: a request touching 100 services with a 1% slow rate each is slow ~63% of the time. The average tells you about the machine; the percentile tells you about the user.
05Estimate storage for 5 years of a photo app with 10M uploads/day.
10M/day × 2.5 MB (original plus thumbnails) = 25 TB/day → ×365×5 ≈ 45 PB before replication; with 3× replication or equivalent erasure coding, budget 1.5–3× that. Metadata is 10M × 1 KB = 10 GB/day → ~18 TB in five years, which is nothing.
The conclusion, which is the actual answer: blobs go to object storage behind a CDN, metadata goes in a database, and tiering matters enormously at 45 PB — old photos are rarely accessed, so lifecycle them to cold storage.
06The interviewer says "assume 1 billion users". What changes?
Say what specifically breaks rather than sprinkling "distributed" everywhere: a single database no longer holds the data or the write rate, so partitioning becomes mandatory rather than optional; a single region can't serve the latency target, so you need multi-region and must now answer the consistency question; fan-out patterns that were linear become the dominant cost; and operational concerns (deploys, migrations, backfills) become multi-week projects rather than afternoons. Then note what doesn't change — the API and the data model usually survive.
APIs & contracts
07REST, GraphQL or gRPC — how do you choose?
REST at the edge because it caches for free at the CDN and every client speaks it. gRPC between internal services because it's binary, multiplexed over HTTP/2, has generated clients and supports streaming. GraphQL when you have many client types with genuinely different data needs and over/under-fetching is a measured problem — and then with persisted queries plus a depth and complexity limit, because an open GraphQL endpoint lets any client write an expensive query against your database.
08Why is cursor pagination better than offset?
Two reasons. Performance: OFFSET 10000 makes the
database scan and discard 10,000 rows, so page 500 costs 500× page 1; a keyset cursor
(WHERE (created_at, id) < (?, ?)) is an index seek that costs the same
at any depth. Correctness: if rows are inserted while a user scrolls,
offsets shift and the user sees duplicates or misses items entirely — a cursor pins a
position in the ordering.
The cost is that you can't jump to page 47 and totals are expensive. That's fine for feeds and logs and wrong for an admin table with page numbers.
09Design an idempotent POST endpoint.
The client generates a UUID per logical intent and sends it as
Idempotency-Key. The server inserts that key with a unique constraint —
the insert is the lock, which is why it's safe under concurrent
retries and not just sequential ones. On conflict: if the stored state is
IN_PROGRESS, return 409; if COMPLETED, return the stored
response body verbatim. Store the response, not just a flag, so a retry is
indistinguishable from the original. Hash the request body and reject a reused key
with different content as a 409 — that's a client bug worth surfacing. Expire keys
after 24 hours, and pass the same key to any downstream provider.
10Which HTTP methods are idempotent, and is PATCH one?
GET, PUT and DELETE are idempotent; POST is not. PATCH is not inherently
idempotent — it depends entirely on the patch semantics.
{"status": "shipped"} is idempotent because it sets a value;
{"count": "+1"} is not because it applies a delta. This distinction —
set versus delta — is the general principle behind natural idempotency everywhere.
11How do you version an API without breaking clients?
For public APIs, URL versioning (/v1/) — ugly but unambiguous and
visible in logs and caches. For internal services, prefer additive-only evolution:
add optional fields with defaults, never remove or repurpose one, never reuse a
Protobuf field number. Ship a Sunset header and per-version usage
metrics, or you will never be able to turn v1 off. Breaking changes that people
forget are breaking: tightening validation, changing a default, and changing the
meaning of an existing field while keeping its type.
Load balancing & caching
12L4 or L7 load balancer — when does it matter?
L4 forwards by IP and port: fast, protocol-agnostic, very high throughput, and it can't make any content-aware decision. L7 parses HTTP, so it can route by path, do TLS termination, header rewriting, retries, canary splits and WAF — at the cost of CPU and one hop of latency. Use L7 for essentially all HTTP services because those features are worth it; use L4 for non-HTTP protocols, extreme throughput, or when you need end-to-end TLS the balancer must not touch.
13Why is least-connections usually better than round robin?
Round robin assumes every request costs the same. When request durations vary — and they always do — a server that got several slow requests keeps receiving new ones on schedule, so queues build unevenly. Least-connections routes by in-flight work, which naturally adapts. Its own failure mode is worth naming: a server that fails instantly has zero connections and therefore looks least loaded, so it attracts all traffic. Passive health checks that eject on error rate fix that.
14Why can deep health checks cause an outage?
If the health check verifies the shared database, then when the database hiccups every server fails its check simultaneously and the load balancer removes the entire fleet — converting a partial degradation into total unavailability. Keep the load-balancer check shallow (is this process able to serve?), monitor dependencies separately, and fail open: if all backends are unhealthy, send traffic anyway, because a degraded response beats no response.
15How do you avoid sticky sessions?
Remove the reason for them: move session state out of server memory into Redis or a signed JWT, so every server can serve every request. That makes autoscaling, deploys and instance failure non-events. Stickiness costs uneven load, session loss on instance death, and awkward scale-in. Legitimate remaining uses are long-lived WebSocket connections (inherently sticky), in-progress upload buffers, and warming local caches — and for that last one, consistent hashing by key is better than a session cookie.
16Cache-aside vs write-through — which and why?
Cache-aside by default: the app reads the cache, and on a miss reads the database and populates. Only requested data is cached, and the cache being down degrades latency rather than breaking writes. Write-through keeps the cache always fresh but makes every write pay both latencies and caches data nobody will read. Write-behind is fastest for writes but loses data if the cache dies before the flush — acceptable for view counts, never for money.
17On a write, do you update the cache or delete the key?
Delete. If two writers both update the cache, they can commit to the database in one order and to the cache in the other, leaving the cache permanently wrong with no self-healing. A delete is correct under every interleaving — the next reader repopulates from truth. And order it correctly: write the database first, then delete the key. Deleting first leaves a window where a concurrent reader repopulates the old value just before the write lands.
18What is a cache stampede and how do you prevent it?
A hot key expires and thousands of concurrent requests all miss and all hit the
database at once, often taking it down. Three defences, used together: a per-key lock
or single-flight so exactly one request recomputes while the others wait or serve
stale; jittered TTLs (300s ± 10%) so keys don't expire in lockstep; and
probabilistic early refresh, where a request nearing the TTL refreshes with rising
probability, so the recompute happens before anything has actually expired.
19How do you handle a hot key that gets 40% of traffic?
Two approaches, and both are worth naming. Replicate the key across N shards with a
suffix (key#0…key#9) and have clients pick one at random,
which spreads the load across nodes at the cost of N× memory and N invalidations. Or
add a small in-process cache in front of Redis, which absorbs the repeats before they
leave the application server — extremely effective for a hot key, at the cost of a
short per-server staleness window.
Databases & sharding
20SQL or NoSQL — how do you actually decide?
Start with SQL unless something specific rules it out, and say why: transactions, joins and ad-hoc queries are enormously valuable while the product is still moving, and a single Postgres primary handles tens of thousands of writes per second, which is past most estimates. Move to NoSQL for a specific reason — write volume beyond one node, a schema that's genuinely heterogeneous, or an access pattern that's purely key-based at massive scale. The strong version of this answer moves one table, not the whole system.
21What makes a good shard key?
Four tests. High cardinality, so it spreads across current and future shards. Even
traffic, not just even data — a balanced dataset where one key takes 30% of
reads is still broken. Present in your hot queries, or every read becomes a
scatter-gather. And it keeps together what's read together — sharding messages by
conversation_id makes loading a chat one hop; sharding by
message_id makes it touch every shard.
22Why consistent hashing instead of hash(key) % N?
Because % N remaps almost everything when N changes — going from 4 to 5
nodes moves roughly 80% of keys, so every cache is cold at once and the database takes
the full unfiltered load. On a hash ring, a new node only takes the arc before it, so
about 1/N of keys move. Add virtual nodes (100–200 points per physical node) so the
distribution is actually even and so a failed node's keys spread across all peers
rather than landing on one unlucky neighbour.
23How do you reshard a live system with no downtime?
Best answer first: avoid it by over-partitioning up front — create 1,024 logical partitions on day one and map many to each physical node, so growth means moving whole partitions rather than rehashing rows. When you must reshard live: add empty shards, dual-write to old and new, backfill history in the background with throttling, verify with checksums, flip reads gradually and reversibly, bake, then stop dual-writing. The verification step is the one candidates skip and the one that catches the bug.
24What does an index cost you?
Every insert, update and delete must maintain every index, so ten indexes on a hot
table means eleven writes per insert — plus memory and disk. Indexes on low-cardinality
columns often won't be used at all (reading half the table via an index is slower than
scanning it), and functions in predicates disable them (WHERE LOWER(email) = ?
can't use an index on email; index the expression instead). Audit for
unused indexes; they're pure write tax.
25Explain the leftmost prefix rule.
A composite index on (a, b, c) is sorted by a, then
b within equal a, then c. So it can serve queries
filtering on a, on a+b, or on a+b+c — but not on
b alone, because the b values are scattered throughout. It's
the same reason a phone book sorted by (surname, first name) is useless for finding
everyone named "Priya".
26What is write skew and why doesn't snapshot isolation prevent it?
Two transactions each read an overlapping set, each sees a state where their action
is valid, and both commit — jointly breaking an invariant that neither broke alone.
The canonical case: two doctors both check "is at least one other doctor on call?",
each sees the other, and both go off call. Snapshot isolation doesn't catch it because
neither transaction read stale data and they wrote different rows, so there's
no write-write conflict to detect. Fix with SERIALIZABLE, an explicit
SELECT … FOR UPDATE on the rows the decision depends on, or a database
constraint that expresses the invariant.
27B-tree or LSM tree — when does the difference matter?
B-trees update in place: predictable read latency, mixed read/write workloads, transactions. LSM trees append to a memtable and flush sequentially: much faster writes, great for ingest-heavy and time-series workloads, at the cost of reads potentially checking several SSTables (mitigated by Bloom filters) and compaction showing up in p99 and consuming background I/O and disk headroom. Choose LSM when writes dominate and you can tolerate a lumpier tail.
28How do you enforce a global unique constraint across shards?
You can't do it inside the sharded table, because no shard sees all rows. Options: shard by the unique attribute so uniqueness is local (works if that's also a good access key); keep a separate global index table keyed on the unique value, written first as a claim and only then the main row; or run a small dedicated uniqueness service. All of them mean the claim and the row are two writes, so you need a cleanup path for claims whose row never landed.
Replication & consistency
29Sync, async or semi-sync replication?
Async is fastest but has a non-zero RPO — a leader failure loses whatever hadn't replicated. Fully synchronous loses no data but makes every write wait for every replica, so one slow replica stalls all writes and any replica failure blocks the system. Semi-synchronous — commit once at least one replica has acknowledged — is usually the right answer: a committed write exists on two machines, and one slow replica out of several doesn't stop you.
30Explain W + R > N.
With N replicas, writing to W and reading from R, if W + R > N the read and write sets must overlap by at least one node, so a read is guaranteed to see the latest acknowledged write. N=3, W=2, R=2 is the balanced default and survives one failure on either path. Two honest caveats: overlap guarantees you can find the newest value but you still need versioning or last-write-wins to order concurrent writes; and sloppy quorums with hinted handoff break the guarantee in exchange for availability during a partition.
31A user posts a comment and doesn't see it after refresh. Why, and how do you fix it?
The write went to the leader and the refresh read from a replica that hasn't caught up — a read-your-writes violation. Fixes, in increasing sophistication: route that user's reads to the leader for a few seconds after their write; or record the write's log position in the session and only read replicas at or past it; or serve the user's own recent writes from a local cache and merge them into the response. Also pin the session to one replica to avoid the related bug, non-monotonic reads, where the comment appears and then vanishes.
32What is causal consistency and when is it the right answer?
It guarantees that if operation A causally precedes B, everyone sees A before B — while concurrent, unrelated operations may be seen in any order. It's the right level for social products: you must never see a reply before the message it replies to, but you don't need a global order over every unrelated post on earth. It's implemented with version vectors or dependency tracking, and it needs no global coordination, which is why it's dramatically cheaper than linearizability.
33What does CAP actually say — and what's the usual mistake?
During a network partition you must choose between consistency and availability. The usual mistake is treating it as "pick two of three": you don't get to choose P, because networks partition whether you like it or not. So real systems are CP or AP, and "CA" describes a single-node system. The second mistake is applying one letter to a whole product — CAP applies per operation, so the same product can be AP for carts and CP for checkout.
34Why is PACELC more useful than CAP?
Because CAP only describes the rare case. PACELC adds: else — when there's no partition — you still choose between latency and consistency. That's the decision you make on every single request, and it's what synchronous cross-region replication actually costs you. Cassandra is PA/EL, Spanner is PC/EC. Framing a design in PACELC terms shows you're thinking about the 99.9% of the time the network is fine.
35What is split brain and how do you prevent it?
The old leader was partitioned, not dead. A new leader is promoted, and now two nodes accept writes and diverge — reconciling them means choosing whose data to throw away. Prevention: require a majority quorum to elect, so a minority partition can never form a leader; use fencing tokens (a monotonically increasing epoch that storage checks, rejecting anything lower) so the old leader's writes are refused on arrival; and STONITH — forcibly kill the old node before promoting.
36Should failover be automatic?
For stateless tiers, yes. For a primary database, many mature teams choose human-confirmed failover, and explaining why is a strong signal: you can't distinguish "dead" from "slow", so a transient blip can trigger a promotion that loses the un-replicated tail of the write log — you've caused an outage to avoid one. The tradeoff is a longer MTTR against a lower chance of self-inflicted data loss, and which you prefer depends on whether the data is a feed or a ledger.
Messaging & async
37Kafka or a queue like SQS?
A queue is for task distribution — one message, one worker, consumed and gone. Kafka is a durable log: multiple independent consumer groups each read everything at their own offset, and you can replay from any point. Choose the log when several different systems care about the same events, when replay matters (new consumer, bug fix, rebuilding a derived store), or when you need strict per-key ordering. Choose a queue when it's genuinely "someone please do this job" and simplicity wins.
38Is exactly-once delivery possible?
Not across a network — the acknowledgement itself can be lost, so the sender can never know whether to resend. What's achievable is at-least-once delivery plus idempotent processing, giving exactly-once effects, and that's what everyone means in practice. Kafka's "exactly-once semantics" is real but scoped to read-process-write where both ends are Kafka, via transactional offset commits; the moment you call an external API inside that loop you're back to at-least-once plus dedup.
39How do you guarantee message ordering?
Don't ask for global ordering — it means one partition, one consumer and no
parallelism. Ask for ordering per key: partition by
conversation_id, account_id or order_id, and all
events for that key land in one partition and are consumed in order. State the three
costs: one key's throughput is capped by one partition, changing partition count
remaps keys and breaks ordering across the change, and a slow message blocks its
partition (head-of-line blocking).
40You write to the database and then publish an event. What's wrong with that?
It's the dual-write problem: two systems, no shared transaction. A crash between them leaves an order in the database that nobody was told about — or, if you publish first, an event for an order that doesn't exist. The fix is the transactional outbox: insert the event into an outbox table in the same database transaction as the business row, and have a separate relay (polling, or CDC via Debezium) publish from it. The relay may publish twice after a crash, which is fine because consumers are idempotent.
41What goes in a dead-letter queue, and then what?
Messages that have exhausted their retries — which should only ever be things that could have succeeded (timeouts, 503s, deadlocks); a 400 or a malformed payload should go to the DLQ immediately rather than burning five attempts. The DLQ is an inbox, not a graveyard: it needs an alert when anything lands, the original message plus the error and stack trace, and a documented and tested replay path for after the fix. A DLQ nobody monitors is a slower way of losing data.
42How do you apply backpressure?
Pick one deliberately rather than letting the queue grow forever: buffer (fine for bounded bursts), throttle the producer (internal pipelines), shed load with 429/503 at the edge (user-facing — a fast rejection beats a five-minute timeout), sample or degrade (telemetry), or autoscale consumers. Name the autoscaling trap: adding workers because the queue is deep can take down the shared database, which slows processing, which deepens the queue. Cap concurrency at what the slowest shared dependency can absorb.
43What is consumer lag and why does it matter more than most metrics?
Lag is the gap between the latest offset and the consumer's committed offset — how far behind you are. It matters because it's the one metric that distinguishes "briefly slow" from "permanently falling behind": flat lag under load is healthy, rising lag means throughput is below arrival rate and the gap will grow without bound. It's also a leading indicator of user-visible symptoms (stale search results, delayed emails) that would otherwise be discovered by customers.
Reliability, rate limiting & locks
44How do retries cause outages?
Two ways. Amplification: three retries at each of four layers is 3⁴ = 81× load on the bottom service, so a service that got slow now gets 81× the traffic and dies properly. Synchronisation: without jitter, everyone who failed at time T retries at T+1 together, a thundering herd hitting a service mid-recovery. Fixes: retry at one layer only (usually the outermost), full jitter on backoff, and retry budgets that cap retries at ~10% of traffic and fail fast beyond that.
45What does a circuit breaker actually protect?
Both sides, but primarily the callee. Closed, calls pass and failures are counted; above a threshold (say 50% errors in a rolling window) it opens and fails instantly without making the call, which stops you hammering a struggling dependency so it can recover. After a cooldown it goes half-open and lets a few probes through: success closes it, failure reopens. The caller benefit is failing in 1 ms instead of waiting 30 s and exhausting its own threads.
46What's a bulkhead?
Resource isolation so one failure can't consume everything — separate connection and thread pools per dependency, so when the recommendations service hangs it exhausts only its own 20 connections and checkout keeps its own. Without bulkheads, one slow dependency drains a shared pool and every endpoint goes down with it. The idea scales up: separate fleets per tenant tier, separate clusters per region, and shuffle sharding, where each tenant gets a random pair of shards so one abusive tenant degrades only the few tenants sharing both.
47Five services at 99.9% each — what's the end-to-end availability?
0.999⁵ = 99.5%, which is about 3.6 hours a month rather than 43 minutes. Dependencies in series multiply, which is the strongest argument for having fewer hard dependencies on the critical path. The corollary worth stating: making a dependency optional — degrade instead of fail — removes it from the product entirely, so "recommendations is best-effort" is an availability improvement, not just a UX decision. Redundant components in parallel go the other way: two 99% replicas give 99.99%.
48Token bucket or sliding window — which and why?
Token bucket for most APIs: tokens refill at a fixed rate up to a capacity, so it enforces an average rate while allowing a controlled burst — which matches how real clients behave. Fixed window is simplest but allows 2× the limit across a boundary (100 requests at 0:59 and 100 at 1:01). Sliding window counter fixes that approximately with two integers. Leaky bucket removes bursts entirely and is right only when the thing you're protecting genuinely cannot absorb them.
49How do you rate limit across 50 servers?
A shared counter in Redis, with the check-and-decrement done in a Lua script so it's atomic — a read-modify-write from the app races and leaks. To keep Redis off the hot path, give each server a local slice of the budget and reconcile every second: slightly over-permissive during the interval, which is nearly always an acceptable trade for removing a network call per request. Then decide fail-open vs fail-closed explicitly: fail open for general API protection, fail closed for anything guarding real cost like SMS or expensive inference.
50Why isn't a Redis lock with a TTL safe?
Because no TTL survives an unbounded pause. Process A takes a 30-second lease, then GC-pauses for 40 seconds. The lease expires, B legitimately acquires it, and A wakes up still believing it holds the lock and writes. Two writers, corrupted state — and any timeout you pick can be exceeded by a pause you didn't predict. The fix is fencing tokens: the lock service issues a monotonically increasing number, and the resource rejects any write with a token lower than the highest it has seen. Note what that requires — cooperation from the resource. If storage won't check a token, the lock can only be made usually right.
51How do you run a cron job exactly once across ten instances?
First ask whether it's an efficiency lock or a correctness lock. For "send the nightly report", a Redis lease with a TTL is fine — a rare double-run wastes CPU and maybe sends a duplicate email, and making the job idempotent removes even that. For anything where a double-run corrupts data, use leader election via etcd or Zookeeper and pass the session's revision number as a fencing token. Better still, design it away: shard the work by key so each instance owns a disjoint slice and no exclusion is needed.
52What's optimistic concurrency and when do you prefer it?
UPDATE … SET value = ?, version = 8 WHERE id = ? AND version = 7 — if
zero rows are updated, someone else won; reload and retry. Prefer it under low
contention, because it takes no locks, needs no lock service, and has no
lock-holder-dies problem. Pessimistic locking wins under high contention, where
optimistic retries would thrash. It's also the mechanism behind HTTP ETags and
If-Match, which is a nice thing to connect.
Platform: observability, storage, search, real-time, uploads
53Logs, metrics or traces — what does each answer?
Metrics answer is something wrong and how much — cheap, bounded, long-retention, good for alerting. Logs answer what exactly happened to this request — expensive, high-detail, short retention. Traces answer where the time went across services — sampled. The thing that makes them observability rather than three tools is a trace ID generated at the edge and attached to every log line and span, so you can pivot between them.
54Why is high cardinality dangerous in metrics?
Every unique combination of label values is a separate time series with its own
storage and index cost. Adding user_id to a metric with a million users
turns 200 series into 200 million and kills the metrics system — usually during an
incident, when you need it most. Labels must be bounded and low-cardinality: status
code, endpoint, region. User IDs, request IDs, full URLs and timestamps belong in
logs and traces, which are built for exactly that.
55Define SLI, SLO, SLA and error budget.
SLI is the measurement (fraction of checkouts under 300 ms with a 2xx). SLO is your internal target (99.9% over 28 rolling days). SLA is the contractual promise with penalties, always set looser than the SLO so you have room. The error budget is 100% − SLO — about 40 minutes of badness a month at 99.9% — and it's the useful invention, because it turns reliability from an argument into arithmetic: budget left, ship; budget spent, freeze and fix.
56What should page a human at 3am?
Symptoms with user impact that a human can act on right now — "checkout error rate above 1% for five minutes", not "CPU above 80%". Prefer burn-rate alerting: page on a fast burn (2% of the monthly error budget in an hour), file a ticket on a slow burn (spread over six hours). Every page must have an action; if the only response is to acknowledge it, it should have been a dashboard. Alert fatigue is a genuine cause of outages, because a team that ignores pages will ignore the one that mattered.
57Object, block or file storage?
Object for immutable blobs addressed by key over HTTP — images, video, backups, logs — effectively unlimited and cheapest. Block for anything needing a filesystem and random writes, like a database volume: sub-millisecond, per-volume size limits, expensive. File for shared POSIX access across machines, usually legacy or scientific workloads. The rule that answers most design questions: blobs in object storage, metadata rows in a database, never binary blobs in a relational database.
58S3 claims eleven nines of durability. What does that not protect you from?
You. Durability is about hardware and media failure; it replicates your
DELETE, your bad migration and your ransomware just as faithfully. You
still need versioning, soft deletes, cross-account or cross-region backups, and
object-lock retention for anything existential. Also distinguish durability from
availability — S3 is ~11 nines durable and ~4 nines available, so the data is
essentially never lost but you may not be able to read it right now. And a backup you
have never restored is a hypothesis.
59How does search ranking actually work at scale?
As a funnel. Retrieval pulls maybe a thousand candidates cheaply from an inverted index using BM25 (or vector similarity); hard filters remove anything ineligible (out of stock, no permission, unsafe); then an expensive re-ranking model scores those thousand on rich features — relevance, popularity, personalisation, freshness, business rules; then blending handles diversity, dedup and pinned results. The whole point is that the expensive model only ever runs on a small candidate set. Feeds and recommendations use the identical shape.
60WebSockets or SSE?
If data only flows server → client — notifications, live scores, dashboards,
streaming tokens — SSE is usually better: it's plain HTTP, so it works with existing
proxies, auth and compression, the browser reconnects automatically, and
Last-Event-ID gives resumption for free. Use WebSockets when the client
genuinely needs to push too — chat, collaborative editing, games — accepting the cost
of connection state, no HTTP caching, and proxy configuration. Defaulting to
WebSockets without considering SSE is a small but common miss.
61How do you handle 1M concurrent WebSocket connections?
Separate the connection tier from the business tier. Stateless gateway nodes hold sockets — plan around 50k–100k per node, so 10–20 nodes — using epoll rather than a thread per connection. A pub/sub backplane (Redis, Kafka, NATS) routes events to whichever gateway holds the recipient, and a registry maps user → gateway with a TTL heartbeat that doubles as presence. Fan out on the gateways so a broadcast is one backplane message rather than 100,000. And handle reconnect storms: if a gateway dies its 60,000 clients all return at once, so jittered client backoff is mandatory.
62How do you handle a resumable 5 GB upload from a phone?
The client requests a presigned URL and uploads directly to object storage — bytes never touch your servers. Use multipart with ~10 MB parts uploaded in parallel, so a dropped connection costs one part rather than the file, and resume is "ask which parts already landed, send the rest". Drive completion from the storage event rather than the client, because clients crash. And set a lifecycle rule to abort incomplete multipart uploads after about seven days — orphaned parts are invisible in listings and you're billed for them forever.
Open-ended prompts and behavioural
63"Design a system to count video views." (A deceptively deep favourite.)
Start by asking what "a view" means and how accurate the count must be — that single question changes everything. If approximate and eventually consistent is acceptable: client fires an event, it goes to Kafka, a stream job aggregates per video per minute, and Redis holds a live counter with periodic flushes to durable storage; use HyperLogLog for unique viewers. If it must be exact and auditable (creator payouts, ad billing), it becomes a ledger problem — deduplicated events keyed on (user, video, session), idempotent processing, and daily reconciliation. Naming both regimes and choosing between them is the whole answer.
64"Design a notification system."
An API that accepts a notification intent, then a pipeline: preference and quiet-hours check → deduplication and rate limiting per user (the actual hard part — nobody should get 50 pushes) → template rendering with localisation → channel routing to APNs, FCM, email or SMS via a provider abstraction → per-provider retry with backoff and a DLQ → delivery status tracking back into the system. Emphasise: idempotency keys so a retry can't double-send, priority queues so a password reset isn't stuck behind a marketing blast, and per-user aggregation windows that collapse "5 people liked your post" into one message.
65"Design a distributed ID generator."
Snowflake: 64 bits as 41 bits of millisecond timestamp + 10 bits of machine ID + 12 bits of per-millisecond sequence. That gives roughly time-sortable, globally unique IDs with no coordination on the hot path, 4,096 per machine per millisecond, and ~69 years of range. Name the failure modes: machine ID assignment needs coordination (Zookeeper or config), and clock skew or NTP going backwards breaks uniqueness — so you refuse to issue IDs while the clock is behind the last-seen timestamp. Compare with UUIDv4 (no coordination at all, but 128 bits and random, so terrible as a B-tree primary key) and UUIDv7 (time-ordered, which fixes the index locality problem).
66The interviewer says "that won't work at scale." What do you do?
Engage, don't defend and don't collapse. Ask which part they mean, restate the constraint you were designing against, and reason about where it actually breaks: "You're right that fan-out on write breaks for celebrity accounts — at 100M followers that's 100M writes per post. Let me special-case accounts above 10,000 followers and pull those at read time instead." Interviewers frequently push back on correct answers to see whether you can hold a position with reasons. Changing your mind for a good reason and holding for a good reason both score; flip-flopping without reasons doesn't.
67How do you avoid over-engineering?
Anchor every component to a number from the estimation phase, and say the trigger that would make you add it: "at 350 writes a second a single Postgres primary is fine — I'd shard when we're past roughly half a node's capacity, which is around X." That reframes simplicity as a deliberate choice with a stated exit rather than ignorance. The strongest version names what you're deliberately not building and why — no service mesh, no multi-region, no CQRS — because the complexity has a real cost in team time and incident surface.
68"What would you do differently with more time?" — how to end the round.
Have two or three genuine answers ready; this is a free chance to show breadth. Good ones: the migration path from the current system, because greenfield is rarely the real situation; the operational story — deployment, backfills, on-call, runbooks; the cost model, since a design that triples the AWS bill for a 5% gain is a bad design; multi-region and data residency; and the things you consciously deferred, named as deferred rather than forgotten. Close with a three-sentence summary and your top two tradeoffs.
The mock drill
Do this once a day for the last week before an interview. It takes 50 minutes and it is the only part of preparation that trains the thing actually being graded.
- Pick a prompt you haven't done. Set a 45-minute timer.
- Speak the whole time, out loud, to an empty room or a recorder. Silent practice trains the wrong skill entirely.
- Draw on paper, not a tool. You'll have a whiteboard or a shared doc, and both are slower than you think.
- Do not look anything up mid-round. Note the gaps and look them up afterwards.
- Play the interviewer for the last 10 minutes: ask yourself the two hardest follow-ups you can think of and answer them.
- Score yourself against the rubric. Where did you go silent? Which box could you not go one level deeper into? Did you state a tradeoff for every choice?
- Write down the one thing you'd change next time, and do it next time.
Interviewers are not checking whether your design matches theirs. They are asking: would I want to be in a design review with this person? Structure your thinking, say the costs out loud, admit what you don't know, and engage with pushback. That behaviour passes rounds even when the design has gaps — and no amount of memorised architecture passes without it.