Top 60 System Design Interview Questions (2026)

The 60 system design questions interviewers actually ask, with direct answers, ASCII architecture sketches, worked estimation math, and what the interviewer is listening for. Grouped into foundations, building blocks, and design walkthroughs.

60 questions with answers

What Is a System Design Interview?

Key Takeaways

  • A system design interview asks you to architect a large service (a chat app, a URL shortener) out loud, choosing components and defending trade-offs rather than writing complete code.
  • It scores judgment, not a single right answer: how you clarify scope, estimate scale, structure a design, and reason about failure under load.
  • The building blocks repeat across every prompt: load balancers, caches, databases, queues, and the trade-offs between consistency, availability, and latency.
  • This page is a question bank. Work the foundations first, learn each building block, then rehearse the classic walkthroughs out loud, because delivery and structure are evaluated too.

A system design interview is an open-ended discussion where you design a large software system, a URL shortener, a news feed, a chat app, while an interviewer probes your reasoning. Unlike a coding round, there's rarely one correct answer. You're evaluated on how you clarify vague requirements, estimate the scale you're designing for, sketch a high-level architecture, then go deep on the hard parts and The trade-offs you accepted. The building blocks recur no matter the prompt: load balancers spread traffic, caches cut latency, databases store state (and get sharded when one machine can't hold it), and message queues decouple services so a slow consumer can't stall a fast producer. Underneath sits a small set of hard truths, the CAP theorem, consistency models, the physics of latency, that shape every choice. AWS publishes the Well-Architected Framework covering these same pillars (reliability, performance, cost, security, operations), and it's a good canonical reference for the vocabulary the question expects. This page collects the 60 questions that come up most, each with a direct answer, and for the classic prompts a clarify to components to hard-problem to trade-offs outline you can rehearse. Increasingly the first design conversation happens inside an AI-led interview, so pair this bank with our AI interview preparation guides for the format side.

60Questions with answers on this page
3Tiers: foundations, building blocks, walkthroughs
6Classic designs walked through at outline depth
45-60 minTypical length of a system design round

Watch: System Design Interview: A Step-By-Step Guide

Video: System Design Interview: A Step-By-Step Guide (ByteByteGo, YouTube)

Test yourself and earn a certificate

10 quick questions. Score 70%+ to download your System Design certificate.

Jump to quiz

All Questions on This Page

60 questions
System Design Foundations
  1. 1. What does a system design interview actually evaluate?
  2. 2. Walk through the standard flow of a system design interview.
  3. 3. What is the difference between functional and non-functional requirements?
  4. 4. How do you do back-of-the-envelope estimation? Work an example.
  5. 5. How do you estimate storage for a system? Work an example.
  6. 6. Why should you have latency numbers memorized, and what are the rough magnitudes?
  7. 7. What is the difference between horizontal and vertical scaling?
  8. 8. Why do we make services stateless, and where does state go?
  9. 9. What is a load balancer and why is it fundamental?
  10. 10. What is the difference between a Layer 4 and a Layer 7 load balancer?
  11. 11. What is caching and why does it matter so much?
  12. 12. Compare cache-aside and write-through caching strategies.
  13. 13. What is cache invalidation and why is it hard?
  14. 14. What is a CDN and when do you use one?
  15. 15. How do you decide between SQL and NoSQL for a given system?
  16. 16. What is a database index and what does it cost?
  17. 17. Explain the CAP theorem honestly.
  18. 18. What are the main consistency models, and how do they differ?
  19. 19. What is the difference between ACID and BASE?
  20. 20. What is a single point of failure and how do you eliminate it?
  21. 21. What is the difference between a forward proxy and a reverse proxy?
  22. 22. What is the difference between normalization and denormalization, and when do you denormalize?
System Design Building Blocks
  1. 23. What is database replication and why do we use it?
  2. 24. What is sharding and how do you choose a shard key?
  3. 25. What is consistent hashing and what problem does it solve?
  4. 26. What is a message queue and why decouple with one?
  5. 27. What is publish-subscribe and how does it differ from a queue?
  6. 28. How do you design a rate limiter?
  7. 29. What is idempotency and why does it matter in distributed systems?
  8. 30. Compare REST, gRPC, and GraphQL for an API.
  9. 31. When do you use WebSockets versus polling for real-time updates?
  10. 32. How do you decide between a monolith and microservices?
  11. 33. Why does each microservice usually own its own database?
  12. 34. What is observability and what are its three pillars?
  13. 35. How should a service retry a failed call, and what can go wrong?
  14. 36. What is a circuit breaker and how does it protect a system?
  15. 37. What is the bulkhead pattern?
  16. 38. What does it mean for a system to degrade gracefully?
  17. 39. How do transactions get harder as a system scales out?
  18. 40. Where do you store large files like images and videos?
  19. 41. How does designing for a read-heavy system differ from a write-heavy one?
  20. 42. What are at-most-once, at-least-once, and exactly-once delivery?
  21. 43. How do systems detect that a node has died and fail over?
Classic System Design Walkthroughs
  1. 44. Design a URL shortener (like TinyURL).
  2. 45. Design a news feed (like a social timeline).
  3. 46. Design a chat / messaging system (like WhatsApp).
  4. 47. Design a distributed rate limiter.
  5. 48. Design a notification system (push, SMS, email).
  6. 49. Design a typeahead / autocomplete (search suggestions).
  7. 50. How do you approach a design prompt you've never seen before?
  8. 51. Design a distributed cache (like a shared Redis layer).
  9. 52. A single database can no longer handle the load. Walk through how you scale it.
  10. 53. Design a system to ingest and analyze high-volume events (like clickstream).
  11. 54. Design a distributed unique ID generator.
  12. 55. Design a web crawler.
  13. 56. Design a metrics and monitoring system.
  14. 57. Design the checkout / order flow for an e-commerce site.
  15. 58. Design a real-time leaderboard.
  16. 59. Design a photo-sharing service (like Instagram).
  17. 60. How do you close out a system design interview strongly?

System Design Foundations

Foundations22 questions

The concepts every design round assumes you know: what's being evaluated, how to estimate scale, and the physics that shape every choice. If any answer here surprises you, that's your study list.

Q1. What does a system design interview actually evaluate?

It evaluates judgment under ambiguity, not memorized architectures. The interviewer watches how you turn a vague prompt into concrete requirements, estimate the scale you're designing for, choose components, and defend the trade-offs you accepted.

There's rarely one right answer. Two candidates can design the same system differently and both pass, if each clarifies scope, reasons out loud, and names what they gave up. Silence and jumping straight to boxes-and-arrows are the two most common ways to fail.

Key point: The interviewer is a collaborator, not an examiner. Think out loud, check assumptions with them, and treat it like a design discussion with a senior colleague.

Watch a deeper explanation

Video: System Design Interview: Step By Step Guide (System Design Interview, YouTube)

Q2. Walk through the standard flow of a system design interview.

Five phases in order. Clarify the requirements (functional and non-functional, scale, read/write mix). Estimate the load with back-of-the-envelope math. Sketch a high-level design of the main components. Deep-dive on the one or two hard parts the interviewer steers toward. Then discuss trade-offs, bottlenecks, and what you'd do next.

Timing matters: spend real minutes on clarify and estimate before drawing anything. A design that solves the wrong problem cleanly still fails.

How a 45-minute round usually splits

1Clarify (5-10 min)
requirements, scope, scale, assumptions written down
2Estimate (5 min)
QPS, storage, bandwidth to size the system
3High-level design (10-15 min)
components and the request flow between them
4Deep dive (10-15 min)
the hard part: sharding, caching, consistency
5Wrap up (5 min)
trade-offs, bottlenecks, failure modes, next steps

Let the interviewer redirect you. Where they push is where the points are; follow their interest into the deep dive.

Key point: Narrate which phase you're in ('let me clarify requirements first'). It shows structure and keeps you from skipping the phase that scores highest.

Q3. What is the difference between functional and non-functional requirements?

Functional requirements are what the system does: shorten a URL, post a message, return search results. Non-functional requirements are how well it does it: how many users, how fast, how available, how consistent, how much data.

Non-functional requirements drive the architecture. 'Support 1,000 users' and 'support 100 million users' are the same functional spec but completely different designs. Pin these numbers down before you draw anything.

TypeQuestion it answersExamples
FunctionalWhat must it do?Shorten URLs, redirect, show analytics
ScaleHow much load?Daily active users, reads/sec, writes/sec
PerformanceHow fast?p99 latency target, throughput
AvailabilityHow reliable?Uptime target, acceptable downtime

Key point: Always ask for the read-to-write ratio early. A read-heavy system (like a news feed) and a write-heavy one need opposite optimizations.

Q4. How do you do back-of-the-envelope estimation? Work an example.

Round everything to clean powers of ten and multiply out loud. Take a service with 100 million daily active users, each making 10 requests a day. That's 100,000,000 x 10 = 1,000,000,000 requests per day (10^9).

Convert to per second: a day is 86,400 seconds, round to 100,000 (10^5). So 10^9 / 10^5 = 10^4 = about 10,000 requests per second average. Peak is usually 2 to 3 times average, so design for roughly 20,000 to 30,000 QPS. Every number here is self-contained arithmetic, not a claimed real-world figure.

python
# Estimation is just arithmetic on round numbers.
daily_active_users = 100_000_000        # 10^8, an assumption
requests_per_user_per_day = 10
requests_per_day = daily_active_users * requests_per_user_per_day
# = 1_000_000_000  (10^9)

seconds_per_day = 100_000                # 86,400 rounded to 10^5
avg_qps = requests_per_day // seconds_per_day
# = 10_000  (10^4)

peak_qps = avg_qps * 3                    # peak is ~2-3x average
# = 30_000  -> design for this

Key point: Memorize the round numbers: a day is ~10^5 seconds, a month is ~2.5 x 10^6. State your assumptions out loud so a wrong guess doesn't sink the whole estimate.

Q5. How do you estimate storage for a system? Work an example.

Multiply items per day by size per item, then by how long you keep them. Say you store 1,000,000 new records a day and each record is 1,000 bytes (1 KB). That's 1,000,000 x 1,000 = 10^9 bytes = 1 GB per day.

Over five years: 1 GB/day x 365 x 5 is about 1,825 GB, round to roughly 2 TB. Add replication (3 copies is common) and you're near 6 TB. Now you know whether this fits one machine or needs sharding, which is the whole point of the estimate.

python
records_per_day = 1_000_000          # 10^6
bytes_per_record = 1_000              # 1 KB
bytes_per_day = records_per_day * bytes_per_record
# = 1_000_000_000  = 1 GB

days = 365 * 5                        # 5 years
total_gb = (bytes_per_day * days) // (10**9)
# ~1825 GB  ->  ~2 TB

with_replication = total_gb * 3       # 3 copies
# ~5.5 TB  -> too big for one node, plan to shard

Key point: The output of a storage estimate is a decision: one machine or many. If the number crosses a few terabytes, you've just motivated sharding before anyone asked.

Q6. Why should you have latency numbers memorized, and what are the rough magnitudes?

Latency intuition tells you where the time goes so you optimize the right thing. Reading from memory is on the order of nanoseconds; reading from SSD is hundreds of microseconds; a network round trip in the same datacenter is under a millisecond; a cross-region round trip is tens to a hundred milliseconds.

The gap is enormous: memory is roughly a million times faster than a cross-continent network hop. That single fact explains caching, why you co-locate services, and why chatty designs that make many sequential network calls feel slow no matter how fast each service is.

Latency of memory vs disk vs network (log scale)

Illustrative round numbers in nanoseconds, so a single scale holds. Memory is nanoseconds; a cross-region network round trip is milliseconds, roughly a million times slower.

L1 cache
1 ns
Main memory (RAM)
100 ns
SSD read
100,000 ns
Network (same datacenter)
500,000 ns
Network (cross-region)
100,000,000 ns
  • L1 cache: ~1 ns: on-chip, effectively free
  • Main memory (RAM): ~100 ns: this is why caches live in memory
  • SSD read: ~100,000 ns (0.1 ms): fast disk, still far slower than RAM
  • Network (same datacenter): ~500,000 ns (0.5 ms): a round trip nearby
  • Network (cross-region): ~100,000,000 ns (100 ms): physics of distance

Key point: You don't need exact numbers, just the orders of magnitude and their ratios. 'Memory is nanoseconds, network is milliseconds' is enough to reason correctly out loud.

Q7. What is the difference between horizontal and vertical scaling?

Vertical scaling means a bigger machine: more CPU, RAM, disk on one box. It's simple, needs no code changes, and avoids distributed complexity, but it hits a hard ceiling (the biggest machine money can buy) and is a single point of failure.

Horizontal scaling means more machines behind a load balancer. It scales almost without limit and tolerates node failure, but it forces you to handle statelessness, data partitioning, and coordination. Most large systems go vertical first for simplicity, then horizontal when they outgrow one box.

Vertical (scale up)Horizontal (scale out)
HowBigger single machineMore machines
CeilingHardware limitEffectively unlimited
FailureSingle point of failureSurvives node loss
ComplexityLow, no code changesHigh, needs partitioning

Key point: Say 'scale up first, scale out when you must.' Reaching for a distributed cluster on day one when a bigger box would do is a classic over-engineering flag.

Watch a deeper explanation

Video: System Design BASICS: Horizontal vs. Vertical Scaling (Gaurav Sen, YouTube)

Q8. Why do we make services stateless, and where does state go?

A stateless service keeps no client-specific data between requests, so any instance can handle any request. That's what makes horizontal scaling and load balancing work: you can add, remove, or restart instances freely, and a failed node loses nothing.

The state has to live somewhere, so push it out: sessions into a shared cache like Redis, data into databases, files into object storage. The application tier stays disposable while the stateful tiers are managed carefully.

text
  Client
    |
    v
[Load Balancer]
    |----------|----------|
    v          v          v
 [App 1]    [App 2]    [App 3]     <- stateless, interchangeable
    |__________|__________|
               v
     [Shared Session Store]        <- state lives here (Redis)
               |
               v
         [Database]

Key point: The one-line rule: 'push state out of the app tier so instances are disposable.' It explains most of why modern services are designed the way they are.

Q9. What is a load balancer and why is it fundamental?

A load balancer sits in front of a pool of servers and spreads incoming requests across them, so no single server is overwhelmed. It's the piece that makes horizontal scaling usable: clients hit one address, the balancer picks a healthy backend.

It also does health checking (stop sending traffic to a dead node), and it's a natural place for TLS termination and sticky sessions. Common algorithms are round robin, least connections, and hashing by a key.

text
         Requests
            |
            v
     +--------------+
     | Load Balancer|  health checks + algorithm
     +--------------+
       /    |    \
      v     v     v
  [Srv A][Srv B][Srv C]
     ok    ok    DOWN  <- LB stops routing to C

Q10. What is the difference between a Layer 4 and a Layer 7 load balancer?

A Layer 4 (transport) load balancer routes on IP and port. It doesn't inspect the request content, so it's fast and cheap, forwarding packets or TCP connections without understanding HTTP.

A Layer 7 (application) load balancer understands HTTP, so it can route by URL path, host header, or cookie, do content-based routing, and terminate TLS. It's more flexible and more expensive per request. Use L7 when you need smart routing (send /api to one pool, /images to another); use L4 when raw throughput matters most.

Layer 4Layer 7
Routes onIP and portHTTP path, headers, cookies
Sees content?NoYes
SpeedFaster, lower overheadSlower, more work per request
Use whenRaw throughput, any protocolSmart HTTP routing, TLS termination

Key point: The follow-up is 'when would you pick L4 over L7?'. Answer: when you don't need to inspect HTTP and want maximum throughput or non-HTTP traffic.

Q11. What is caching and why does it matter so much?

A cache stores the results of expensive work (a database query, a computed page) in fast storage so repeat requests skip the work. Because memory is roughly a million times faster than a cross-region network call and far faster than disk, a cache hit turns a slow operation into a fast one.

Caching is the single most effective tool for read-heavy systems. It cuts latency, reduces load on the database, and lets you serve far more traffic from the same backend. The whole discipline is deciding what to cache, where, and how to keep it fresh.

text
Request
   |
   v
[Cache] --hit--> return fast (memory: ~nanoseconds)
   |
  miss
   |
   v
[Database] --> compute, store in cache, return (slower)

Key point: Frame caching as a trade: you trade freshness and memory for latency and throughput. Every cache question is really about that trade.

Watch a deeper explanation

Video: Cache Systems Every Developer Should Know (ByteByteGo, YouTube)

Q12. Compare cache-aside and write-through caching strategies.

Cache-aside (lazy loading): the app checks the cache, and on a miss reads the database then populates the cache. Simple and only caches what's actually requested, but the first read after any miss is slow, and stale data lingers until it expires.

Write-through: every write goes to the cache and the database together, so the cache is always fresh. Reads are fast and consistent, but writes are slower (two writes) and you cache data that may never be read. Write-back (write to cache, flush to DB later) is faster still but risks data loss on crash.

StrategyHow it worksBest for / cost
Cache-asideApp loads cache on missRead-heavy; first read is slow, can go stale
Write-throughWrite to cache + DB togetherConsistency; slower writes
Write-backWrite to cache, flush to DB laterWrite-heavy; risks loss on crash
Read-throughCache loads from DB itself on missSimpler app code; cache owns loading

Key point: Cache-aside is the default answer for read-heavy systems. Mention write-through when the interviewer pushes on consistency of cached data.

Q13. What is cache invalidation and why is it hard?

Cache invalidation is removing or updating cached data when the underlying source changes, so readers don't see stale values. It's famously hard because the cache and the source are separate, and keeping them in sync across many nodes, with concurrent writes, has real edge cases.

The practical tools: TTLs (expire entries after a set time, accepting bounded staleness), explicit invalidation on write (delete the key when the data changes), and versioned keys. Most systems combine a short TTL with invalidation-on-write, accepting that perfect freshness is expensive.

Key point: Say the honest thing: perfect cache freshness is impossible without giving up the speed the cache exists for. The real question is how much staleness the product can tolerate.

Q14. What is a CDN and when do you use one?

A content delivery network is a fleet of caching servers spread across geographic locations. It stores copies of static content (images, video, CSS, JS) close to users, so a request is served from a nearby edge node instead of crossing the globe to your origin.

Use a CDN for anything static and cacheable, and increasingly for cacheable API responses at the edge. It cuts latency dramatically for a global audience and offloads huge traffic from your origin. The trade-off is invalidation lag when content changes and cost per gigabyte served.

Key point: Whenever a prompt mentions global users or serving images/video, reach for a CDN early. Not mentioning one for a media-heavy system is a visible miss.

Q15. How do you decide between SQL and NoSQL for a given system?

Match the database to the data and access patterns, not to fashion. Pick SQL when you need transactions, joins, ad-hoc queries, and strong consistency, and the data is naturally relational: payments, orders, anything where correctness beats raw scale. Pick NoSQL when you have huge write volume, simple key-based access, a flexible or evolving schema, and you can tolerate eventual consistency: activity feeds, session data, logging.

The strong move is to say 'it depends' and then commit. The access pattern, state which property matters most (consistency vs write scale), and justify the pick for this problem. Many real systems use both: SQL for the core transactional data, NoSQL for the high-volume peripheral data.

NeedLean SQLLean NoSQL
Transactions across rowsYesUsually not
Joins and ad-hoc queriesYesAwkward
Massive write throughputHarderYes
Flexible / evolving schemaRigidYes

Key point: Never answer 'NoSQL because it scales' with no context. the choice maps to the access pattern; that's the difference between a buzzword and a decision.

Q16. What is a database index and what does it cost?

An index is a separate data structure (usually a B-tree) that lets the database find rows by a column value without scanning the whole table, turning an O(n) scan into an O(log n) lookup. It's the first fix for a slow query that filters or sorts on a column.

The cost is real: every index takes storage and slows writes, because each insert, update, or delete must also update the indexes. So you index the columns you query on, not every column, and you watch write-heavy tables carefully.

Key point: The trade to state out loud: indexes speed reads and slow writes. On a write-heavy table, too many indexes is a bottleneck, not a feature.

Q17. Explain the CAP theorem honestly.

CAP says a distributed system can guarantee at most two of Consistency, Availability, and Partition tolerance. But partitions (network failures between nodes) are a fact of life you can't wish away, so partition tolerance isn't optional. That means the real choice, during a partition, is between consistency and availability.

Honestly: when the network splits, do you refuse to answer rather than return possibly-stale data (consistency, a CP system like a bank ledger), or do you keep answering and reconcile later (availability, an AP system like a social feed)? When there's no partition, you get both. CAP is a lens for one specific failure, not a permanent label on a database.

Key point: Avoid the myth 'pick two of three.' The nuanced answer, partitions are unavoidable so it's really C vs A during a partition, signals you actually understand it.

Q18. What are the main consistency models, and how do they differ?

Strong consistency means every read returns the most recent write; there's a single agreed-upon truth. It's the easiest to reason about and the most expensive, because it needs coordination across nodes, which adds latency and hurts availability during partitions.

Eventual consistency means replicas converge over time, so a read might return a slightly stale value briefly, but the system stays fast and available. In between sit models like read-your-writes (you always see your own updates). The choice is per-feature: a bank balance needs strong consistency, a like-count tolerates eventual.

Key point: Consistency is a spectrum you choose per feature, not one setting for the whole system. Naming a feature that's fine with eventual consistency shows practical judgment.

Q19. What is the difference between ACID and BASE?

ACID (Atomicity, Consistency, Isolation, Durability) is the guarantee model of traditional relational databases: transactions are all-or-nothing, isolated from each other, and durable once committed. It prioritizes correctness, which is why you want it for money and orders.

BASE (Basically Available, Soft state, Eventual consistency) is the looser model many NoSQL systems adopt to get horizontal scale and availability. It accepts temporary inconsistency in exchange for staying up and fast under load. The pair maps directly onto the SQL-vs-NoSQL and consistency-vs-availability trade-offs.

Key point: ACID/BASE back connects to CAP and SQL/NoSQL rather than reciting the acronyms. the question needs the through-line, not a definition dump.

Q20. What is a single point of failure and how do you eliminate it?

A single point of failure is any component whose failure takes down the whole system: one database, one load balancer, one instance holding state. Finding and removing them is a core reliability goal.

The fix is redundancy at every tier: multiple app instances behind a load balancer, replicated databases with failover, and even redundant load balancers (an active-passive pair with a floating address). The question to keep asking is 'what happens if this box dies?' until every answer is 'traffic shifts and users don't notice.'

Key point: When you draw an architecture, point at each box and ask 'what if this dies?' out loud. Catching your own single points of failure before the interviewer does is a strong signal.

Q21. What is the difference between a forward proxy and a reverse proxy?

A forward proxy sits in front of clients and makes requests on their behalf: the servers it talks to see the proxy, not the real client. It's used for outbound control, caching, and anonymity inside a network.

A reverse proxy sits in front of servers: clients think they're talking to it, and it forwards to whichever backend it chooses. That single entry point is where you do load balancing, TLS termination, caching, and rate limiting, which is why a reverse proxy shows up at the edge of almost every system design. A load balancer is one specialized kind of reverse proxy.

text
Forward proxy:  [Client] -> [Forward Proxy] -> [Internet]
                (hides the client from the server)

Reverse proxy:  [Client] -> [Reverse Proxy] -> [Backends]
                (hides the backends; does LB, TLS, caching)

Key point: The one-liner: forward proxy fronts clients, reverse proxy fronts servers. The reverse proxy is the one that matters in most design answers.

Q22. What is the difference between normalization and denormalization, and when do you denormalize?

Normalization splits data into related tables with no duplication, so each fact lives in exactly one place. It keeps writes clean and prevents inconsistencies, but reads often need joins across many tables. It's the default for transactional SQL systems.

Denormalization deliberately duplicates data (or precomputes joined views) so a read hits one place instead of joining. You denormalize read-heavy paths where join cost hurts, accepting that a single fact now lives in several rows you must keep in sync on write. It's a read-latency-for-write-complexity trade, and it's why NoSQL data models often look pre-joined.

NormalizedDenormalized
DuplicationNoneDeliberate
ReadsNeed joinsSingle lookup, fast
WritesSimple, consistentMust update copies
FitsTransactional SQLRead-heavy paths, NoSQL

Key point: Denormalization is a read optimization you pay for on writes. Framing it as that trade, not as 'NoSQL is just messy,' is the mature answer.

Back to question list

System Design Building Blocks

Building Blocks21 questions

The reusable parts every design is assembled from: databases at scale, queues, APIs, rate limiting, and the failure-handling patterns that keep systems up. Know these cold and any walkthrough becomes assembly.

Q23. What is database replication and why do we use it?

Replication keeps copies of the same data on multiple database nodes. The common shape is leader-follower (primary-replica): writes go to the leader, which streams changes to followers that serve reads. This scales reads, provides redundancy, and lets you fail over to a follower if the leader dies.

The catch is replication lag: followers are slightly behind the leader, so a read right after a write can miss it. You handle that with read-your-writes routing (send a user's reads to the leader briefly after they write) or by accepting bounded staleness for reads that can tolerate it.

text
        Writes
          |
          v
     [ Leader ] ---- replicates ----+
          |                          |
     (async stream)                  v
          |                     [Follower 1] <- reads
          +------------------> [Follower 2] <- reads

Key point: Replication scales reads, not writes. If the prompt is write-heavy, replication alone won't save you, that's what leads into sharding.

Q24. What is sharding and how do you choose a shard key?

Sharding (horizontal partitioning) splits one logical database across many machines, each holding a subset of the rows. It's how you scale writes and storage past what a single node can hold, since each shard handles only its slice of the data and traffic.

The shard key decides which shard a row lives on, and choosing it well is the whole game. A good key spreads data and load evenly and keeps related rows together to avoid cross-shard queries. A bad key creates hotspots (one shard doing all the work) or forces expensive scatter-gather queries across every shard.

text
  Rows keyed by user_id
         |
   shard = hash(user_id) % N
         |
  +------+------+------+
  v      v      v      v
[Sh 0] [Sh 1] [Sh 2] [Sh 3]   <- each holds ~1/N of the data

Key point: The follow-up is always 'what makes a bad shard key?'. Answer: one that creates hotspots (uneven load) or forces cross-shard queries. Have an example ready.

Watch a deeper explanation

Video: What is DATABASE SHARDING? (Gaurav Sen, YouTube)

Q25. What is consistent hashing and what problem does it solve?

Plain hash-mod-N sharding breaks when N changes: add or remove one node and hash(key) % N remaps almost every key, forcing a massive data reshuffle. Consistent hashing fixes that. It maps both keys and nodes onto a ring (a hash space), and each key belongs to the next node clockwise.

When a node joins or leaves, only the keys between it and its neighbor move; the rest stay put. Virtual nodes (many ring positions per physical node) smooth out the distribution. It's the backbone of distributed caches and databases like Cassandra and DynamoDB.

text
        Node A
         *  . . . key1 -> A
      .           .
  key3          Node B
    .              *
 Node C  . . . key2 -> B
    *
 Adding a node steals only the arc between it and its
 successor; every other key keeps its home.

Key point: Lead with the problem it solves: 'hash mod N remaps everything when N changes.' Naming the pain before the solution is what makes this answer land.

Q26. What is a message queue and why decouple with one?

A message queue is a buffer between a producer that creates work and a consumer that processes it. The producer drops a message and moves on; the consumer pulls it when ready. This decouples the two: a slow or offline consumer can't stall the producer, and a burst of work queues up instead of overwhelming anyone.

It also enables async processing (return to the user instantly, do the slow work later), load leveling (absorb spikes), and retries (redeliver a failed message). The cost is added complexity and eventual, not immediate, processing.

text
[Producer] --push--> [ Queue: msg msg msg ] --pull--> [Consumer]
                          ^ buffers bursts,
                            survives slow consumers

Key point: Reach for a queue whenever a request triggers slow work the user doesn't need to wait for: sending email, encoding video, updating a feed. 'Do it async' is often the right instinct.

Q27. What is publish-subscribe and how does it differ from a queue?

In a plain queue, each message is consumed by exactly one worker: it's for distributing work. In publish-subscribe, a publisher sends to a topic and every subscriber gets its own copy: it's for broadcasting events.

Pub-sub lets many independent services react to the same event without the publisher knowing who they are. An 'order placed' event can simultaneously trigger billing, inventory, email, and analytics, each subscribing independently. It's the backbone of event-driven architectures.

text
               +--> [Billing]
[Publisher] -> (Topic) --> [Inventory]
               +--> [Email]
               +--> [Analytics]
 one event, every subscriber gets a copy

Key point: The distinction interviewers check: queue is one-to-one (work), pub-sub is one-to-many (events). Mixing them up is a common tell.

Q28. How do you design a rate limiter?

A rate limiter caps how many requests a client can make in a window, protecting the system from abuse and overload. The token bucket algorithm is the common pick: a bucket refills tokens at a fixed rate up to a capacity, each request spends a token, and requests with no token available are rejected. It allows short bursts while enforcing an average rate.

Single process: keep a bucket per user in memory. Distributed across many servers: the state has to move to a shared store like Redis (atomic increment plus expiry, or a Lua script), because per-process counters can't see global traffic. Return 429 with a Retry-After header on rejection.

python
import time

class TokenBucket:
    def __init__(self, rate, capacity):
        self.rate, self.capacity = rate, capacity
        self.tokens, self.updated = capacity, time.monotonic()

    def allow(self):
        now = time.monotonic()
        # refill based on elapsed time
        self.tokens = min(self.capacity,
                          self.tokens + (now - self.updated) * self.rate)
        self.updated = now
        if self.tokens >= 1:
            self.tokens -= 1
            return True
        return False

Key point: Distributed rate limiting is the real follow-up: where does the counter live so every server agrees? 'Redis with atomic operations' is the expected answer.

Q29. What is idempotency and why does it matter in distributed systems?

An operation is idempotent if doing it many times has the same effect as doing it once. It matters because networks are unreliable: a client sends a request, times out without a response, and retries, but the first request may have succeeded. Without idempotency, that retry double-charges a card or creates two orders.

The standard technique is an idempotency key: the client sends a unique key with the request, and the server records it. If the same key arrives again, the server returns the stored result instead of re-executing. Naturally idempotent operations (set X to 5) need no key; side-effecting ones (charge $10) do.

python
def charge(idempotency_key, amount):
    prior = store.get(idempotency_key)
    if prior is not None:
        return prior            # retry: return the same result
    result = payment_gateway.charge(amount)
    store.set(idempotency_key, result)
    return result               # first time: do the work once

Key point: Bring up idempotency yourself whenever a design involves payments, orders, or any retryable write. Volunteering it signals real distributed-systems experience.

Q30. Compare REST, gRPC, and GraphQL for an API.

REST uses HTTP verbs on resources, returns JSON, and is simple, cacheable, and universally understood: the default for public APIs. Its weaknesses are over-fetching (you get the whole resource) and under-fetching (many round trips for related data).

gRPC uses HTTP/2 and binary protobuf, which makes it fast and strongly typed with generated clients: ideal for internal service-to-service calls where performance matters. It's less browser-friendly. GraphQL lets the client request exactly the fields it needs in one query, killing over- and under-fetching, at the cost of server complexity and harder caching. Pick by audience: REST for public, gRPC for internal, GraphQL for rich clients with varied data needs.

StyleBest atWatch out for
RESTPublic APIs, simplicity, cachingOver- and under-fetching
gRPCFast internal service-to-serviceNot browser-native, binary
GraphQLFlexible client-driven queriesServer complexity, caching is hard

Key point: Don't declare one universally best. 'REST for public, gRPC internal, GraphQL for flexible clients' shows you choose by context, which is the whole point.

Q31. When do you use WebSockets versus polling for real-time updates?

Short polling (client asks 'anything new?' on a timer) is simple but wasteful: most requests return nothing, and updates lag by the poll interval. Long polling (the server holds the request open until there's data) cuts waste but still reconnects per message.

WebSockets open a single persistent, bidirectional connection, so the server pushes updates instantly with no repeated handshakes: the right choice for chat, live feeds, collaborative editing, anything genuinely real-time and high-frequency. The cost is stateful connections that complicate load balancing and scaling. For occasional updates, polling or server-sent events are simpler.

ApproachBest forCost
Short pollingOccasional updates, simplicityWasteful, laggy
Long pollingNear-real-time, simple infraReconnect per message
WebSocketsChat, live feeds, high frequencyStateful connections, harder to scale
Server-sent eventsOne-way server pushOne-directional only

Key point: Match the tool to update frequency. Reaching for WebSockets when a 30-second poll would do is over-engineering; polling for a chat app is under-engineering.

Q32. How do you decide between a monolith and microservices?

A monolith is one deployable application. It's the right default for a new product or small team: simpler to build, test, deploy, and debug, with fast in-process calls and no distributed-systems tax. The pain comes at scale, one large codebase, coupled deploys, and the whole app scaling together.

Microservices split the system into independently deployable services, letting teams own and scale pieces separately. That autonomy is real, but so is the cost: network latency between services, distributed failures, data consistency across service boundaries, and heavy operational tooling. The honest answer is 'start monolith, extract services when team size and scale actually demand it,' not 'microservices because they're modern.'

Key point: The mature take is 'monolith first.' Proposing microservices for a greenfield small-team system is a classic over-engineering red flag interviewers watch for.

Q33. Why does each microservice usually own its own database?

If services share one database, they're coupled through the schema: a change one team makes can break another, and you're back to a distributed monolith with none of the autonomy. Giving each service its own database keeps them independent, so a team can evolve its data model and scale its store without coordinating.

The cost is that you lose cross-service joins and single-database transactions. You get data via APIs or events instead, and you handle multi-service operations with patterns like the saga (a sequence of local transactions with compensating actions on failure). This is where eventual consistency enters most microservice designs.

Key point: Naming the saga pattern for a cross-service transaction is a strong production signal. It shows you know you can't just wrap two services in one ACID transaction.

Q34. What is observability and what are its three pillars?

Observability is your ability to understand what a running system is doing from the outside, which is essential once you have many services and can't just attach a debugger. The three pillars are logs (discrete timestamped events), metrics (aggregated numbers over time like request rate and error rate), and traces (the path of one request across all the services it touched).

Together they answer different questions: metrics tell you something is wrong (error rate spiked), traces tell you where (which service in the chain), and logs tell you why (the exact error). A production design that doesn't mention monitoring and alerting is incomplete.

Key point: Mention observability in practice when wrapping up a design. 'How would we know this is healthy?' is a question senior interviewers love to hear you ask yourself.

Q35. How should a service retry a failed call, and what can go wrong?

Transient failures (a brief timeout, a blip) are worth retrying, but naive retries are dangerous. Retrying immediately and in lockstep across many clients can create a retry storm that hammers a recovering service into staying down. The fix is exponential backoff (wait longer after each failure) plus jitter (randomize the delay) so clients don't all retry at the same instant.

Also cap the number of retries and only retry idempotent operations, otherwise a retry might duplicate a side effect. Retries should be paired with timeouts so a slow call fails fast instead of hanging.

python
import random, time

def call_with_backoff(fn, max_tries=5):
    for attempt in range(max_tries):
        try:
            return fn()
        except TransientError:
            if attempt == max_tries - 1:
                raise
            # exponential backoff + jitter
            delay = (2 ** attempt) + random.random()
            time.sleep(delay)

Key point: Always add jitter, not just backoff. 'Exponential backoff with jitter' is the phrase that shows you've been burned by a synchronized retry storm before.

Q36. What is a circuit breaker and how does it protect a system?

A circuit breaker wraps calls to a dependency and tracks failures. When failures cross a threshold it 'trips' and stops sending calls for a cooldown period, immediately failing them instead. This does two things: it lets the struggling dependency recover instead of being hammered, and it lets callers fail fast rather than piling up on slow, doomed requests.

After the cooldown it goes 'half-open,' lets a trial request through, and closes again if that succeeds. It's the standard defense against cascading failures, where one slow service drags down everything calling it.

text
  CLOSED ---(failures exceed threshold)---> OPEN
    ^                                          |
    |                                    (cooldown)
 (trial ok)                                    v
    +----------- HALF-OPEN <--(let one request through)

Key point: Pair 'retries' and 'circuit breaker' in your answer. Retries handle blips; circuit breakers stop a dead dependency from taking the caller down with it.

Q37. What is the bulkhead pattern?

Named after ship compartments that stop one flooded section from sinking the whole vessel, the bulkhead pattern isolates resources so a failure in one part can't exhaust everything. In practice you give each dependency or tenant its own pool of connections or threads, rather than one shared pool.

Without bulkheads, one slow dependency can consume every thread in a shared pool, and suddenly requests that don't even use that dependency start failing too. Isolating pools contains the blast radius to just the affected feature.

Key point: Bulkheads plus circuit breakers plus timeouts are the trio for graceful degradation. Naming all three when asked about resilience indicates production experience.

Q38. What does it mean for a system to degrade gracefully?

Graceful degradation means the system keeps serving a reduced but useful experience when a part fails, instead of returning an error page. If the recommendation service is down, show a generic list instead of nothing. If the live like-count is unavailable, show a cached or approximate number.

You design for it by ranking features into core and non-core, and by having fallbacks (cached data, defaults, feature flags to shed load). The mindset is that partial availability beats total failure, and users forgive a missing sidebar far more than a blank screen.

Key point: When discussing failure, don't stop at 'it errors.' Describing a fallback ('serve stale data', 'hide the non-core widget') shows product-aware engineering judgment.

Q39. How do transactions get harder as a system scales out?

On a single database, ACID transactions are easy: the database coordinates them. Once data is sharded or split across services, a transaction spanning multiple nodes needs distributed coordination, which is slow and fragile. Two-phase commit provides it but blocks and doesn't tolerate coordinator failure well, so large systems mostly avoid it.

The common alternative is the saga: model the operation as a series of local transactions, each with a compensating action to undo it if a later step fails. You trade strict atomicity for availability and accept eventual consistency, then design the compensations carefully.

Key point: 'Just use a distributed transaction' is a weak answer at scale. Reaching for the saga pattern with compensating actions shows you know two-phase commit's costs.

Q40. Where do you store large files like images and videos?

Not in the database. Large binary files go in object storage (S3-style blob stores), which is built for cheap, durable, effectively unlimited storage of files. The database stores only metadata and a reference (the object key or URL), keeping it small and fast.

Clients then upload and download directly to object storage using pre-signed URLs, so the file bytes never flow through your application servers, and a CDN sits in front to serve popular files from the edge. Putting multi-megabyte blobs in a relational database is a classic anti-pattern that bloats and slows it.

Key point: The pattern to state: 'files in object storage, metadata in the database, CDN in front.' Any media-heavy design (photos, video) should follow it.

Q41. How does designing for a read-heavy system differ from a write-heavy one?

Read-heavy systems (news feeds, product catalogs) lean on caching and read replicas: push data close to readers, add replicas to fan out reads, and precompute expensive views. The database's write path is rarely the bottleneck.

Write-heavy systems (logging, metrics, activity tracking) can't cache their way out, since caching helps reads. They need sharding to spread writes, sometimes a write-optimized store (LSM-tree databases), buffering writes through a queue, and batching. Identifying which one you're dealing with, from the read-to-write ratio you asked for early, steers the whole design.

Where scaling effort goes: read-heavy vs write-heavy

Illustrative relative emphasis (arbitrary units) to contrast the two profiles, not measured values.

Read-heavy: caching + replicas
80 effort
Read-heavy: write path
20 effort
Write-heavy: sharding + queues
85 effort
Write-heavy: caching
15 effort
  • Read-heavy: caching + replicas: Cache aggressively, add read replicas, precompute views
  • Read-heavy: write path: Writes are rarely the bottleneck here
  • Write-heavy: sharding + queues: Spread writes across shards, buffer and batch
  • Write-heavy: caching: Caching helps reads, so it barely moves the needle

Key point: This is why you ask for the read-to-write ratio in the clarify phase. It decides whether you spend your design budget on caches or on shards.

Q42. What are at-most-once, at-least-once, and exactly-once delivery?

At-most-once: send and forget. A message might be lost but never duplicated. It's the cheapest and fine for data you can afford to drop, like some metrics.

At-least-once: keep retrying until acknowledged, so nothing is lost, but a message can arrive more than once (the ack got lost, so you resend). This is the common default, and it's why consumers must be idempotent. Exactly-once is the ideal but is genuinely hard end-to-end; in practice you get it by combining at-least-once delivery with idempotent processing or deduplication on the consumer, rather than magic in the queue.

text
at-most-once   : send once, no retry     -> may lose, never dup
at-least-once  : retry until ack          -> never lose, may dup
exactly-once   : at-least-once + dedup    -> effectively once
                 (dedup/idempotency on the consumer)

Key point: When someone says 'exactly-once,' probe it: real systems usually achieve at-least-once plus idempotent consumers. Saying that shows you know the guarantee isn't free.

Q43. How do systems detect that a node has died and fail over?

Nodes send periodic heartbeats (a health signal) to a monitor or to each other. If heartbeats stop for a threshold, the node is presumed dead and traffic is routed elsewhere: a load balancer stops sending to it, or a replica is promoted to take over.

The subtlety is that a missed heartbeat can mean a dead node or just a slow network, and being too aggressive causes false failovers (declaring healthy nodes dead), while being too slow leaves you serving errors. Leader election protocols coordinate who takes over so two nodes don't both think they're the new leader (split-brain).

Key point: The two failure modes of failover: too twitchy causes false positives, too slow causes downtime. split-brain matters.

Back to question list

Classic System Design Walkthroughs

Design Walkthroughs17 questions

The prompts that come up again and again, each at outline depth: clarify, components, the one hard problem, and the trade-offs. Rehearse these out loud until the The technical sequence is automatic.

Q44. Design a URL shortener (like TinyURL).

Clarify: we shorten long URLs to short codes, redirect on lookup, and it's massively read-heavy (far more redirects than creations). Ask about custom aliases, link expiry, and analytics. Estimate to decide storage: if you create 1,000,000 links a day and keep them 5 years, that's about 1.8 billion links, small enough that the hard part is fast reads, not storage.

Components: an API service, a database mapping short code to long URL, and a heavy cache in front (redirects are the same lookup over and over, so cache hit rate is huge). Put it behind a load balancer and a CDN.

Hard problem: generating the short code. Two clean options. Encode an auto-incrementing counter in base62 (a-z, A-Z, 0-9), which gives short, unique, collision-free codes but needs a distributed counter. Or hash the URL and take the first few characters, which risks collisions you must detect and resolve. Base62 of a counter is the usual pick.

Trade-offs: caching gives fast reads but the analytics count goes eventually-consistent; a distributed ID generator adds a component but removes collision handling. State that you optimized for read latency at the cost of write-path complexity.

text
Create:  [Client] -> [LB] -> [API] -> counter -> base62 -> [DB]

Redirect (the hot path):
  [Client] -> [CDN] -> [LB] -> [API]
                                 |
                          [Cache] --hit--> 301 redirect (fast)
                                 |
                                miss
                                 v
                              [DB] -> cache it -> redirect

Key point: Lead with 'this is read-heavy' and design around the cache. The short-code generation is the fun part, but the read path is where the scale actually is.

Q45. Design a news feed (like a social timeline).

Clarify: users follow others and see a timeline of their posts, newest first. It's read-heavy, and the key tension is when to build each user's feed. Ask about feed ranking (chronological vs ranked) and how many people a user can follow.

Components: a post service, a follower/graph service, a feed store (often a per-user list in a fast cache), plus fan-out workers driven by a message queue.

Hard problem: fan-out. Fan-out-on-write (push): when someone posts, immediately write it into every follower's feed. Reads are then instant, but a celebrity with millions of followers causes a write explosion. Fan-out-on-read (pull): build the feed on demand by pulling recent posts from everyone a user follows. Cheap writes, but slow reads for users following many people. The standard answer is a hybrid: push for normal users, pull for celebrity accounts, then merge.

Trade-offs: push spends storage and write work to make reads fast (right for read-heavy feeds); pull saves storage but makes reads expensive. The hybrid is more complex but handles the follower-count skew that breaks either pure approach.

text
Post -> [Post Service] -> [Queue] -> [Fan-out Workers]
                                          |
            push into each follower's feed  v
                                    [Per-user Feed Cache]
                                          ^
  Read feed <-------------------------- (already assembled)
  (celebrities handled via pull-on-read + merge)

Key point: The fan-out write-vs-read trade is the entire question. Naming the celebrity problem and proposing a hybrid is what separates a pass from a great answer.

Q46. Design a chat / messaging system (like WhatsApp).

Clarify: one-to-one messaging first, then group chat; needs real-time delivery, online/offline presence, and delivery receipts. Ask about message history retention and group size limits.

Components: WebSocket servers holding persistent client connections (chat is genuinely real-time, so not polling), a message store, a presence service, and a queue for offline delivery. A connection-routing layer tracks which server holds which user's socket.

Hard problem: delivering a message when the recipient is offline or connected to a different server. You need a way to find the recipient's active connection (a routing table mapping user to server), and if they're offline you persist the message and push it when they reconnect. Ordering and exactly-once-feeling delivery (dedup with message IDs) are the fiddly parts.

Trade-offs: persistent WebSocket connections give instant delivery but are stateful, which complicates load balancing and scaling (you can't just round-robin). You accept that connection state in exchange for real-time push, and lean on the queue plus message store to make offline delivery reliable.

text
[User A] ==WebSocket== [Chat Server 1]
                            |
                    [Routing / Presence] -- who is B on?
                            |
[User B] ==WebSocket== [Chat Server 2]
                            |
   offline? -> [Message Store] + [Queue] -> deliver on reconnect

Key point: The stateful-connection problem is the crux: WebSockets don't load-balance like stateless HTTP. Showing you know why is the differentiator here.

Watch a deeper explanation

Video: WHATSAPP System Design: Chat Messaging Systems for Interviews (Gaurav Sen, YouTube)

Q47. Design a distributed rate limiter.

Clarify: what are we limiting (requests per user, per IP, per API key), what limits (100 requests per minute?), and does it need to be exact or approximate? Where does it sit, at the API gateway or inside each service?

Components: a limiter that runs at the gateway in front of your services, backed by a fast shared store (Redis) so every gateway instance sees the same counts. Token bucket or sliding window is the algorithm.

Hard problem: making it work across many gateway instances. A per-process counter is useless because a user's requests hit different instances, so the count must live in one shared place, updated atomically (Redis INCR with expiry, or a Lua script for the bucket logic) to avoid race conditions where two instances both think there's room.

Trade-offs: a centralized Redis store gives accurate global limits but adds a network hop and a dependency (and a potential single point of failure you must replicate). A purely local limiter is faster but only approximate. Many systems accept slightly fuzzy limits for speed, and return 429 with Retry-After on rejection.

text
[Client] -> [Gateway A] --+
                          |--> [Redis] atomic counter per user
[Client] -> [Gateway B] --+     (INCR + EXPIRE / Lua bucket)
                                    |
            over limit? --> 429 Too Many Requests + Retry-After

Key point: The single word that makes this answer work is 'shared.' The counter can't be local, or independent instances undercount. Redis with atomic ops is the expected home for it.

Q48. Design a notification system (push, SMS, email).

Clarify: which channels (push, SMS, email), what volume, and do we need delivery guarantees, user preferences, and rate limits so we don't spam people? Are notifications triggered by events or scheduled?

Components: a notification API that accepts requests, a message queue to decouple sending from triggering, per-channel worker pools (one for push, one for SMS, one for email), and adapters to third-party providers. A preferences service checks whether the user opted in, and a template service formats content.

Hard problem: reliable delivery through flaky third-party providers. Providers fail and rate-limit you, so workers need retries with backoff, a dead-letter queue for messages that keep failing, and idempotency so a retry doesn't send the same notification twice. Fan-out to multiple channels should be independent, so a failing SMS provider doesn't block emails.

Trade-offs: the queue makes the system resilient and async but means delivery is eventual, not instant. Per-channel isolation (bulkheads) adds components but contains provider failures. You accept added moving parts for reliability and the ability to absorb bursts.

text
[Event] -> [Notification API] -> [Queue]
                                    |
        +---------------+-----------+-----------+
        v               v                       v
  [Push Workers]   [SMS Workers]          [Email Workers]
        |               |                       |
   [APNs/FCM]      [SMS Provider]          [Email Provider]
   retries + backoff + dead-letter queue + idempotency keys

Key point: Idempotency and retries are the heart of this one: nobody forgives a system that sends the same push five times. Bring both up before the interviewer asks.

Watch a deeper explanation

Video: System Design Interview - Notification Service (System Design Interview, YouTube)

Q49. Design a typeahead / autocomplete (search suggestions).

Clarify: suggest completions as the user types, ranked by popularity, returning within a few tens of milliseconds because it fires on every keystroke. Ask how many suggestions, and whether they're personalized or global.

Components: a trie (prefix tree) holding the corpus of query strings, precomputed with the top suggestions cached at each node so a lookup is a single tree walk, not a search. Serve it from memory behind a service, with an aggregation pipeline updating popularity counts offline.

Hard problem: latency at every keystroke plus keeping suggestions fresh. You can't run a ranking query per keystroke, so you precompute the top-k completions for each prefix and store them at that trie node; a request just walks to the node and returns the cached list. Updating popularity happens asynchronously (batch jobs aggregate search logs) rather than on the hot read path.

Trade-offs: precomputing top-k per prefix trades memory and update lag for blazing-fast reads, which is the right call because reads vastly outnumber updates. Suggestions being minutes-stale is fine; a slow typeahead is not.

text
Type 'sy' :  walk trie  s -> y
                              |
              node 'sy' caches top-k: [system design,
                                       symptoms, syntax]
  Reads: one in-memory tree walk, no ranking query.
  Updates: offline batch job recomputes counts, refreshes nodes.

Key point: The insight is 'precompute, don't search.' Explaining that you cache top-k completions at each trie node, updated offline, is exactly what this prompt is testing.

Q50. How do you approach a design prompt you've never seen before?

Fall back on the loop, not on memory. Any unfamiliar prompt yields to the same five steps: clarify requirements and scale, estimate the load, sketch high-level components, deep-dive the hardest part, and name trade-offs. The prompt changes; the method doesn't.

Map the new problem onto building blocks you know. Almost every system is some combination of load balancing, caching, a database (maybe sharded and replicated), queues for async work, and a plan for failure. If you understand those parts and the loop, you can reason your way through 'design a system for X' even having never seen X before, which is precisely the skill being tested.

Key point: When you get a prompt you've never rehearsed, say the loop out loud and start clarifying. Interviewers often prefer a novel prompt specifically to see if you have a method or just memorized answers.

Q51. Design a distributed cache (like a shared Redis layer).

Clarify: a fast key-value store shared across many app servers, holding hot data to offload the database. Ask about size (does it fit one node?), eviction policy, and whether stale reads are acceptable.

Components: a cluster of cache nodes, a client that knows how to route a key to the right node, and an eviction policy (LRU is the default: evict least-recently-used when full). Data is partitioned across nodes so the total cache is bigger than one machine.

Hard problem: distributing keys across nodes so that adding or removing a node doesn't blow away the whole cache. Plain hash-mod-N remaps almost everything when the node count changes, causing a cache stampede against the database. Consistent hashing solves it: only the keys near the changed node move. Replication of hot keys adds availability if a node dies.

Trade-offs: consistent hashing keeps the cache warm through membership changes at the cost of slightly more complex routing. A larger cache spread over more nodes improves hit rate but adds coordination. You accept eventual consistency between cache and database, managed with TTLs and invalidation.

text
[App] --key--> hash onto ring --> [Cache Node]

        Node1
         *  . key A -> Node1
     .          .
  key C       Node2
     .          *  . key B -> Node2
    Node3 . . .
     *
 Add/remove a node -> only neighboring keys move (consistent hashing)

Key point: This is where consistent hashing pays off concretely. Connecting it to 'avoid a cache stampede when a node changes' shows you understand why the algorithm exists, not just what it is.

Q52. A single database can no longer handle the load. Walk through how you scale it.

Escalate in order of complexity, cheapest first. Start by adding read replicas if reads are the pressure, which fans out read traffic while writes stay on the leader. Add a cache in front to absorb repeated reads before they ever reach the database.

If writes or storage are the ceiling, next comes sharding: split the data across nodes by a shard key so each handles a slice. Before that, tune what you have, add indexes, optimize slow queries, and denormalize hot read paths, because sharding is a big commitment.

Hard problem: choosing when to shard and on what key, since sharding removes easy joins and cross-shard transactions. Trade-offs: replicas and caching are low-risk and handle read-heavy growth; sharding unlocks write scale but permanently complicates the data layer. Reach for it only when simpler steps are exhausted.

text
Step 1  add cache        [App] -> [Cache] -> [DB]
Step 2  add replicas     writes -> [Leader] -> [Replicas] <- reads
Step 3  tune + index     kill slow queries, denormalize hot paths
Step 4  shard            split by shard key across [DB0][DB1][DB2]
         (last resort: loses joins and cross-shard transactions)

Key point: Present the escalation ladder in order: cache and replicas before sharding. Jumping straight to sharding signals you reach for the big hammer before the cheap fixes.

Q53. Design a system to ingest and analyze high-volume events (like clickstream).

Clarify: we ingest a firehose of events (clicks, views) and answer analytics queries over them. It's extremely write-heavy on ingest and read-heavy on the query side, and those two profiles want different stores. Ask whether queries need real-time or can be batch, and how long we keep raw data.

Components: a queue or log (a durable event stream) as the ingestion buffer that absorbs bursts, stream processors that aggregate, a raw store (object storage or a columnar warehouse) for the full data, and a serving store with precomputed rollups for fast queries.

Hard problem: the ingest rate is far too high to write straight into a queryable database and too high to query raw. So you decouple: buffer through the log, aggregate in the stream layer, and precompute rollups (counts per minute, per user) that the query side reads cheaply. Raw data lands in cheap columnar storage for occasional deep queries.

Trade-offs: precomputed aggregates make dashboards instant but fix the questions you can answer quickly; raw storage keeps flexibility but is slow to query. Buffering through a log adds latency (analytics are near-real-time, not instant) in exchange for surviving huge ingest spikes.

text
[Events] -> [Ingest Log / Queue] (absorbs the firehose)
                    |
                    v
           [Stream Processors] --> [Rollup Store] <- fast queries
                    |
                    v
           [Raw Columnar Store]  <- deep / ad-hoc queries (slow)

Key point: Separate the write path from the read path explicitly. High-volume ingest and fast analytics queries almost never want the same store, and naming both is the point.

Q54. Design a distributed unique ID generator.

Clarify: we need unique IDs across many machines, ideally roughly time-sortable, at high throughput, without a central bottleneck. Ask whether IDs must be strictly monotonic or just unique and mostly increasing.

Components: the Snowflake-style approach packs an ID into 64 bits: a timestamp (so IDs sort by time), a machine/worker ID (so different machines never collide), and a per-machine sequence counter (for multiple IDs in the same millisecond). Each machine generates IDs locally, no coordination per ID.

Hard problem: uniqueness without a central counter that would bottleneck and become a single point of failure. Snowflake solves it by giving each machine its own ID space via the machine-ID bits, so machines never step on each other. Clock skew and clock-going-backwards are the edge cases you must handle (wait, or refuse to issue).

Trade-offs: a central auto-increment is dead simple and strictly sequential but a scaling bottleneck and a single point of failure. Snowflake scales linearly and is roughly time-ordered but only approximately monotonic across machines, and it depends on reasonably synced clocks. For most systems that trade is worth it.

text
 64-bit ID (Snowflake style):
 +-------------------+-----------+--------------+
 |  timestamp (ms)   | machine   |  sequence    |
 +-------------------+-----------+--------------+
   sortable by time   no collision  many per ms
 Each machine generates locally -> no per-ID coordination

Key point: Frame it as 'avoid a central counter.' The Snowflake bit-packing exists precisely so machines never coordinate per ID, which is the scaling insight the question tests.

Q55. Design a web crawler.

Clarify: fetch pages starting from seed URLs, follow links, and store content, at scale and politely. Ask about scope (whole web vs a set of sites), freshness (recrawl cadence), and whether we respect robots.txt (yes).

Components: a URL frontier (a prioritized queue of URLs to fetch), a pool of fetcher workers, a parser that extracts links and content, a store for page content, and a 'seen URLs' set to avoid re-crawling the same page. The frontier and the seen-set are the coordination points.

Hard problem: not re-crawling the same URL and being polite to each host. Deduplication needs a fast membership check over billions of URLs (a hash set, often backed by a bloom filter to save memory). Politeness means rate-limiting per domain so you don't hammer one server, which pushes you toward per-host queues in the frontier.

Trade-offs: a bloom filter for the seen-set saves enormous memory but allows rare false positives (skipping a page you haven't seen), usually an acceptable trade at web scale. Per-host politeness limits throughput per domain but keeps you from getting blocked. Distributing the frontier across machines adds coordination but is required for scale.

text
[Seed URLs] -> [URL Frontier (per-host queues)] -> [Fetchers]
                        ^                               |
                        |                          [Parser]
                        |                          /       \
                 add new links <----------------- links   content
                        |                                    |
                 [Seen set / bloom filter]              [Store]

Key point: Politeness (per-host rate limiting) and dedup (a bloom filter for seen URLs) are the two details interviewers dig for. Volunteer both and you've hit the core of the prompt.

Q56. Design a metrics and monitoring system.

Clarify: collect time-series metrics (request rate, latency, error rate) from many services, store them efficiently, and support dashboards and alerts. Ask about resolution (per-second vs per-minute) and retention.

Components: agents on each service pushing (or a central poller pulling) metrics, an ingestion layer, a time-series database optimized for timestamped numeric data, a query and dashboard layer, and an alerting engine that evaluates rules against incoming data.

Hard problem: the volume of time-series data is enormous and mostly only recent data is queried at full resolution. So you downsample over time (keep per-second data for a day, roll up to per-minute for a week, per-hour for a year), which shrinks storage dramatically while keeping trends. The time-series database is specialized precisely because relational stores handle this poorly.

Trade-offs: downsampling saves huge storage but loses fine detail on old data, which is almost always fine. Push vs pull collection each have costs (push scales with service count, pull centralizes control). You accept approximate old data for affordable retention.

text
[Services] --metrics--> [Ingestion] -> [Time-Series DB]
                                             |
     downsample: 1s (1 day) -> 1m (1 week) -> 1h (1 year)
                                             |
                        +--------------------+-------------+
                        v                                  v
                  [Dashboards]                      [Alerting Engine]

Key point: Downsampling over time is the storage insight here. Explaining that you keep recent data fine-grained and roll up old data shows you understand time-series workloads specifically.

Q57. Design the checkout / order flow for an e-commerce site.

Clarify: a user places an order, we reserve inventory, charge payment, and create the order, reliably. This is the opposite of a social feed: correctness beats raw scale, so it wants strong consistency and careful failure handling. Ask about inventory reservation and payment retry behavior.

Components: an order service, an inventory service, a payment service (talking to an external gateway), all coordinated. The core data (orders, inventory, payments) belongs in a SQL database for transactions and consistency.

Hard problem: the operation spans services and an external payment provider, so a naive distributed transaction won't do. You use a saga: reserve inventory, then charge payment, then confirm the order, with compensating actions (release inventory, refund) if a later step fails. Payment must be idempotent (an idempotency key) so a network retry can't double-charge.

Trade-offs: choosing SQL and a saga prioritizes correctness (no lost orders, no double charges) over the write scale a NoSQL feed system would chase. You accept more coordination and eventual consistency across services in exchange for never taking money without creating an order, which is non-negotiable for commerce.

text
Saga:
  1. reserve inventory  --fail--> (nothing to undo)
  2. charge payment     --fail--> release inventory
  3. create order       --fail--> refund + release inventory
  Payment uses an idempotency key so retries never double-charge.

Key point: Flip the usual instinct: this prompt rewards choosing consistency (SQL, saga, idempotency) over scale. Reaching for NoSQL and eventual consistency on a payment flow is the wrong signal here.

Q58. Design a real-time leaderboard.

Clarify: rank users by score, show the top N and a given user's rank, updated in near-real-time as scores change. Ask how many users (thousands vs hundreds of millions) and how fresh ranks must be, because that decides the approach.

Components: a sorted structure keyed by score. Redis sorted sets are the go-to: they keep members ordered by score and give O(log n) inserts and O(log n + k) range reads, so top-N and rank-of-user are both cheap operations out of the box.

Hard problem: computing a specific user's rank efficiently at large scale. A sorted set answers rank directly for moderate sizes; at very large scale you partition by score range or bucket approximate ranks, because exact global rank over hundreds of millions on every read gets expensive. Ties and simultaneous updates need a consistent tiebreak (timestamp).

Trade-offs: a Redis sorted set gives exact ranks and instant updates but lives in memory, so the data set must fit (or be sharded). Approximating rank at extreme scale trades exactness for speed. You pick based on the user count you clarified up front.

text
Redis sorted set 'leaderboard':
  ZADD leaderboard 980 alice      # update score, O(log n)
  ZREVRANGE leaderboard 0 9       # top 10, O(log n + k)
  ZREVRANK leaderboard alice      # alice's rank, O(log n)
 At extreme scale: shard by score range or bucket approx ranks.

Key point: Naming the exact data structure (a sorted set) and its operations is the win here. It shows you match a specialized tool to the access pattern instead of hand-rolling ranking.

Q59. Design a photo-sharing service (like Instagram).

Clarify: users upload photos, follow others, and see a feed of photos from people they follow. It's read-heavy on the feed and involves large binary files, so it combines the news-feed problem with media storage. Ask about photo sizes, feed ranking, and whether we generate thumbnails.

Components: an upload service, object storage for the photo bytes (not the database), a metadata database (who posted what, when), a CDN in front of the images, the fan-out feed machinery from the news-feed design, and an async worker that generates resized thumbnails after upload.

Hard problem: serving large images fast to a global audience while keeping the database small. The bytes live in object storage with only a reference in the database, uploads go straight to storage via pre-signed URLs so they never touch app servers, and a CDN caches popular images at the edge. Thumbnail generation runs off a queue so the upload response is instant.

Trade-offs: separating blobs (object storage plus CDN) from metadata (database) keeps each store doing what it's good at, at the cost of more moving parts. Precomputed thumbnails spend storage to make reads fast. The feed inherits the same fan-out write-vs-read trade as any timeline.

text
Upload:  [Client] --pre-signed URL--> [Object Storage] (bytes)
              |                               |
              v                          [Queue] -> [Thumbnail Worker]
        [Metadata DB] (who/when/ref)

View:    [Client] -> [CDN] -> image bytes (edge-cached)
         feed assembled via fan-out (see the news-feed design)

Key point: The insight that ties it together: 'bytes in object storage + CDN, metadata in the database, thumbnails async.' It reuses the news-feed fan-out plus the blob-storage pattern.

Q60. How do you close out a system design interview strongly?

Summarize and self-critique. Restate the design in a sentence, then proactively name its bottlenecks and single points of failure before the interviewer has to, and say how you'd address each. This shows engineering maturity: real systems are never finished, and you know where yours would strain first.

Then mention what you deliberately left out for time (security hardening, detailed monitoring, cost optimization, multi-region) and how you'd extend the design for 10x the scale. Ending with 'here's what I'd do next and where this would break first' lands far better than trailing off, and it invites the interviewer into a final discussion rather than an awkward silence.

Key point: Reserve two minutes to critique your own design. Candidates who spot their own weak points read as senior; candidates who present a design as flawless read as inexperienced.

Back to question list

Why Trade-offs? SQL vs NoSQL, and Monolith vs Microservices

Every system design answer is a chain of trade-offs, and two show up in almost every interview: which database family to pick, and how to slice the application. SQL databases give you strong consistency, joins, and transactions, and they scale vertically until sharding gets painful. NoSQL trades some of that for horizontal scale and flexible schemas, which fits huge write volumes and simple access patterns. A monolith is one deployable unit: simple to build, test, and reason about, but every change ships together. Microservices split the system so teams deploy independently and scale hot paths in isolation, at the cost of network calls, distributed failures, and operational overhead. There's no universally right choice, and saying that out loud, then justifying yours for the specific problem, is exactly the signal interviewers screen for.

ChoiceBest atTrade-off
SQL (relational)Transactions, joins, strong consistency, well-understood queriesHarder to scale writes horizontally; sharding adds real complexity
NoSQL (document, key-value, wide-column)Huge write volume, horizontal scale, flexible schema, simple lookupsWeaker consistency by default; joins and ad-hoc queries are awkward
MonolithFast early development, simple deploys, easy local reasoning and testingWhole app ships together; one hot path can force scaling everything
MicroservicesIndependent deploys, isolated scaling, team autonomy on large systemsNetwork calls, distributed failures, and heavy operational overhead

How to Prepare for a System Design Interview

Prepare the approach, not a catalog of answers. The prompts vary, but strong candidates run the same loop every time: clarify what's actually being asked, estimate the scale, sketch a high-level design, go deep on the one or two hard parts, then name trade-offs. Rehearse that loop until it's automatic, and practice out loud with a timer, because your structure and communication as much as your architecture is the technical point.

  • Learn the building blocks cold: load balancers, caches, SQL vs NoSQL, replication, sharding, queues. You can't design with parts you don't understand.
  • Practice back-of-the-envelope estimation until the arithmetic is quick. Reads per second, storage per year, and cache size drive every downstream choice.
  • Rehearse the classic prompts (URL shortener, news feed, chat, rate limiter, typeahead) out loud, following clarify to components to hard-problem to trade-offs each time.
  • Take the quiz on this page to find weak spots, then revisit those questions before the real round.

The system design interview approach

1Clarify requirements
functional and non-functional scope, scale, read/write mix, constraints
2Estimate
back-of-envelope QPS, storage, and bandwidth to size the system
3High-level design
draw the main components and how a request flows between them
4Deep dive
go deep on the one or two hard parts the interviewer cares about
5Trade-offs
name what you gave up, discuss bottlenecks, failure, and next steps

Spend real time on clarify and estimate. Candidates who jump straight to boxes-and-arrows usually design the wrong system well.

Test Yourself: System Design Quiz

Ready to test your System Design knowledge?

10 questions, about 6 minutes. Score 70% or higher to earn a shareable certificate.

10 questions Instant feedback Free certificate on 70%+

Frequently  Asked  Questions

Which System Design topics should I prioritize before a technical round?

Prioritize the areas that change real decisions: fundamentals, practical tasks, debugging, trade-offs, and evidence. The highest-value questions are tied to a project example, a validation check, or a failure mode.

Do I need to write code in a system design interview?

Rarely full programs. You'll draw components and discuss trade-offs, and you might sketch a data model, a small algorithm (a token bucket, a consistent-hash lookup), or an API signature. The snippets on this page are that kind: short and illustrative. The evaluation is about architecture and reasoning, not syntax.

How much math do I need for the estimation part?

Only arithmetic you can do out loud: multiplication, powers of ten, and rounding to clean numbers. The point isn't precision, it's sizing. Knowing whether you're designing for a thousand requests per second or a million changes every downstream choice, so practice the worked examples on this page until the numbers come quickly.

Are these questions enough to pass a system design interview?

They cover the concepts and the classic prompts well, but a real round is a live conversation with follow-ups tailored to whatever you claim confidence in. talking through each walkthrough out loud with a timer is the practice path. Our AI coding interview prep guide covers the live format, including how automated evaluation reads the structure of your reasoning.

How long does it take to prepare?

If you already build software, two to three weeks of an hour a day covers the building blocks and a handful of walkthroughs with practice runs. Starting colder, plan four to six weeks and draw the classic designs by hand repeatedly, because reading them without sketching them out loud is how preparation quietly fails.

Is there a way to test my system design knowledge?

Yes. The quiz on this page scores you as you go and explains every answer, so you find your weak spots fast. Pass the threshold and you can download a shareable certificate with your name and score, free and with no sign-up. It is the quickest way to check yourself before the real round.

From the team that builds coding interviews

Hyring builds the AI Coding Interviewer that runs live technical rounds in 20+ languages, and system design discussions are part of what we help teams evaluate. These questions reflect what actually gets asked and evaluated inside the interviews we host for 5,000+ HR teams.

See how the AI Coding Interviewer works

Sources

Adithyan RKWritten by Adithyan RK
Surya N
Fact-checked by Surya N
Published on: 20 May 2026Last updated: 3 Jul 2026
Share: