Archtin
All articles
System DesignInterviewScalability14 min read

Top 10 System Design Interview Questions (With Answers)

The ten system design interview questions that come up again and again — hot databases, 10M-user APIs, Instagram likes, dead caches, Kafka lag, geo search, duplicate payments, rate limiters, interview openings and ChatGPT at scale.

Most system design interviews are not original. Interviewers reach for a small set of scenarios because those scenarios reliably separate people who have operated systems from people who have only read about them. Below are ten of those scenarios: a short answer you could give in ninety seconds, the trap most candidates fall into, and a link to the full deep dive when you want the reasoning behind the answer.

How to use this list

Read the short answer first and try to say it out loud from memory. If you can restate it without notes — including the numbers and the trade-off — you have the question. If you cannot, open the deep dive. The goal is not to memorise ten answers; it is to internalise the four or five habits that produce all ten: measure before you scale, isolate failure, make writes idempotent, and always name the trade-off you are accepting.

1. Your database is at 100% CPU. What do you do?

Short answer: do not add replicas yet. Find the queries burning the CPU (pg_stat_statements ordered by total time), check whether they are missing an index or doing sequential scans, and look for a recent deploy or a cache that stopped working. Most 100% CPU incidents are one bad query plan, one missing index, or a cache miss storm — not genuine capacity exhaustion. Only after you have ruled those out do you scale: read replicas for read-heavy load, connection pooling if you are drowning in connections, then sharding as the last resort.

The trap: jumping to "add read replicas" makes replication lag your new problem while the bad query still burns CPU on every node.

Deep dive: Your database is at 100% CPU — a triage playbook

2. What happens when 10M users hit your API?

Short answer: walk the request path and name what breaks at each hop: DNS and CDN absorb static traffic, the load balancer runs out of connections, the app tier runs out of worker threads, the database runs out of connections, and finally something downstream times out and cascades. The fixes map one-to-one: cache aggressively at the edge, make the app tier stateless and horizontally scalable, put a connection pooler in front of the database, add timeouts, retries with jitter, circuit breakers and bulkheads so one slow dependency cannot consume every thread.

The trap: talking about "auto-scaling" without noticing that the database connection count scales with the number of app instances.

Deep dive: What actually happens when 10M users hit your API

3. How does Instagram handle 1 billion likes?

Short answer: a like is not a row you read on every page view. You write the like to a durable store keyed by (post_id, user_id) for idempotency and "did I like this", and you keep the count as a separately maintained aggregate — incremented asynchronously, batched, and served from a cache. Counts are eventually consistent by design; nobody notices whether a post has 12,481 or 12,483 likes, but everyone notices a 2-second page load.

The trap: SELECT COUNT(*) on a hot post, or a single counter row that becomes a write hotspot. Shard counters, or aggregate from a stream.

Deep dive: How Instagram handles 1 billion likes

4. Your Redis cache suddenly became useless.

Short answer: the hit rate collapsed for a reason. The usual suspects are a key format change after a deploy (every key is now a miss), eviction pressure because the working set outgrew maxmemory, a TTL that is too short or synchronised so everything expires together, or a cold restart. Diagnose with INFO stats — keyspace hits vs misses, evicted_keys, expired_keys. Fix with correct key versioning, jittered TTLs, request coalescing to prevent stampedes, and enough memory headroom.

The trap: blaming Redis. Redis is almost always fine; the caching strategy around it is what broke.

Deep dive: Your Redis cache suddenly became useless

5. Your Kafka consumer is getting slower. Now what?

Short answer: separate three different problems that all look like "lag": the consumer is slow per message (profile the handler, batch the database writes), the consumer cannot parallelise (partition count caps your consumers, so repartition), or the consumer is stuck on one poison message and rebalancing in a loop. Watch consumer lag per partition, not in aggregate — a single hot partition is the most common cause and aggregate lag hides it.

The trap: adding consumer instances beyond the partition count. Those extra consumers sit idle.

Deep dive: Your Kafka consumer is getting slower

6. How does Blinkit find the nearest delivery partner?

Short answer: you never scan all riders and compute distances. You index location on a grid — geohash, S2 cells, or H3 hexagons — so "near me" becomes a lookup of a few cell IDs plus their neighbours. Rider positions live in a hot, frequently-overwritten store (Redis geospatial sets or an in-memory shard per city), not in your primary database. Then you rank candidates by ETA rather than straight-line distance, and factor in batching, load and fairness.

The trap: designing a beautiful nearest-neighbour query and ignoring that every rider sends a location update every few seconds — the write volume is the hard part.

Deep dive: How Blinkit finds the nearest delivery partner

7. Users are getting duplicate payments. Find the bug.

Short answer: a duplicate payment is almost never a duplicate button click; it is a retry meeting a non-idempotent write. The client timed out and retried, or a queue redelivered, or a gateway webhook arrived twice. The fix is an idempotency key generated by the client, stored with a unique constraint before the charge is attempted, so the second attempt returns the first result instead of charging again. Add a state machine for the payment, and reconcile against the provider daily.

The trap: disabling the button, or checking "does a payment already exist" without a unique constraint — two concurrent requests both read "no" and both charge.

Deep dive: Users are getting duplicate payments — find the bug

8. Design a rate limiter for 10M users.

Short answer: pick the algorithm from the requirement — token bucket if you want to allow bursts, sliding window counter if you want accurate per-minute limits, fixed window only if you can tolerate the boundary spike. Then solve the distributed part: state in Redis with an atomic Lua script, keyed by user or API key, with local in-process pre-filtering to keep Redis traffic down. Fail open or closed deliberately, and always return 429 with Retry-After.

The trap: a per-instance counter. With twenty instances the effective limit is twenty times what you promised.

Deep dive: Designing a rate limiter for 10M users

9. How do you start ANY system design interview?

Short answer: spend the first five to eight minutes not designing. Clarify scope and pick two or three features. Establish scale with rough numbers — daily active users, requests per second, read/write ratio, data size per year. State your non-functional targets: latency, availability, consistency. Sketch the API. Only then draw boxes. Every later decision references those numbers, which is what makes the design feel deliberate rather than recited.

The trap: drawing a load balancer in the first thirty seconds.

Deep dive: How to start any system design interview

10. How would you design ChatGPT at scale?

Short answer: the interesting parts are not the model. They are: streaming responses over SSE so users see the first token in under a second; a GPU inference tier with request batching, a queue and admission control because GPUs are the scarce, expensive resource; conversation state and context-window management; quotas and abuse controls per account; and safety filtering on both input and output. Capacity planning is measured in tokens per second per GPU, not requests per second.

The trap: treating an LLM call like a 50ms REST call. It is a multi-second, stateful, expensive stream, and everything about the architecture follows from that.

Deep dive: How to design ChatGPT at scale

The patterns behind all ten

Look at the ten answers together and the same handful of ideas keeps reappearing. That is the real syllabus.

Recurring ideaWhere it showed up
Measure before you scale100% CPU, Kafka lag, cache hit rate
Precompute and cache the read pathInstagram likes, ChatGPT context, geo cells
Idempotency for every retryable writeDuplicate payments, Kafka redelivery
Isolate failure (timeouts, bulkheads, circuit breakers)10M users, LLM inference tier
Shared state must be atomicRate limiter, sharded counters
Choose consistency deliberatelyLike counts, rider positions, quotas
Capacity in units that matterConnections, partitions, tokens/sec — not just RPS
A useful habit
In every answer above, the strongest version names a number and a trade-off. "We cache counts with a 30-second TTL, so counts can be up to 30 seconds stale — acceptable for likes, not for account balances." That single sentence pattern is what separates a senior answer from a correct one.

Summary

  • These ten scenarios cover most of what senior system design interviews test.
  • Diagnose before scaling: the majority of "we need more capacity" incidents are one bad query, one broken cache, or one hot partition.
  • Any write that can be retried needs an idempotency key and a unique constraint.
  • Counts, feeds and locations are precomputed and eventually consistent; money is not.
  • Start every interview with scope, numbers and targets — then design against them.
  1. Read the short answers and say them out loud from memory.
  2. Open the deep dive for anything you could not reconstruct.
  3. Practise each one as a 35-minute mock, out loud, on a whiteboard.

Keep reading

Suggested next articles based on this one.

Design it, don't just read it.

Practise LLD and system design problems with structured rubrics and AI feedback.

Start practising free