Field guide · 6 parts · 36 topics · 14 case studies · 19 system videos

System Design Casebook

Learn system design the way interviews and production outages teach it. Start with one server, add each component only when a number forces you to, and defend every choice with an estimate. Every system design opens with a short animated video of it at work, tradeoffs marked, and every topic gives you a diagram to step through or a simulator to break.

  1. Part IFoundationsCore definitions, request anatomy, percentiles, availability, queueing, data modelsBasic
  2. Part IIMethodThe 45 minute plan, envelope math, latency numbersBasic
  3. Part IIIScalingFrom one box to millions of users in nine stepsBasic
  4. Part IVBuilding blocksLoad balancers, caches, hashing, CAP, queues, rate limitsIntermediate
  5. Part VCase studiesFourteen full designs, URL shortener to payments, plus a use case mapBasic to advanced
  6. Part VIReviewFifty most asked questions, a tradeoff sheet and a quizMixed
Part I

Foundations

The vocabulary and the handful of basic facts every design discussion assumes. Learn these first; every later part builds on them.

I.1Basic

Core definitions

The terms that come up in every design conversation, one or two lines each. Search them, filter by area, and follow a link to see the idea at work elsewhere in the guide.

I.2Basic

Anatomy of a request

Before your code runs, a request pays for a name lookup and handshakes, and each one costs round trips. Knowing where the time goes explains why CDNs, connection reuse and HTTP/3 exist.

Round trip time (RTT)
Time for a packet to reach the other side and come back. Distance sets the floor: light in fiber covers about 200 km per millisecond.
DNS
Turns a name like example.com into an IP address. Answers are cached by browsers, operating systems and resolvers for the record’s TTL.
TCP handshake
SYN, SYN-ACK, ACK: one round trip before any data can flow.
TLS handshake
Agrees on keys to encrypt the connection: two round trips with TLS 1.2, one with TLS 1.3.
Time to first byte (TTFB)
From starting the request until the first byte of the response arrives. Setup, one round trip and server work all count.
Connection reuse
Keeping a connection open (keep-alive, pooling) so later requests skip DNS and both handshakes.

Simulator: where the time goes

Server distance
Protocol
Connection setup
0 ms
Time to first byte
0 ms
Total
0 ms

Assumes the resolver already has the DNS answer cached, 60 ms of server work and a 200 KB response over a 50 Mbps link.

How a name becomes an address

Four hops in the worst case, zero in the usual one. The resolver walks from the root to the authoritative server only when nothing is cached. Every answer is cached for its TTL along the way, which is why DNS changes take time to spread and why a short TTL helps with failover.
I.3Basic

Latency, throughput and percentiles

Latency is how long one request takes; throughput is how many you finish per second. Averages hide the slow requests users actually feel, so serious systems are measured in percentiles.

Latency
Time for one operation, measured end to end. Usually reported in milliseconds.
Throughput
Work completed per unit of time: requests per second, messages per second, MB per second.
Bandwidth
The maximum rate a link can carry. Throughput is what you actually get through it.
Percentile
p99 = 250 ms means 99% of requests finished within 250 ms and 1 in 100 took longer. p50 is the median.
Tail latency
The slow end of the distribution, p99 and beyond. It grows in importance as one user action fans out into many calls.
Mean
The average. A few very slow requests drag it far above what a typical user sees, so it describes nobody.

Simulator: 10,000 response times

p50, median
0
Mean
0
p95
0
p99
0

Why the tail matters more than it looks

I.4Basic

Availability and the nines

Availability is the share of time a system does its job. Each extra nine is ten times harder to reach, and every component you chain together brings its own failures.

Availability
Uptime as a share of total time, such as 99.9%. Measured over a window, often 30 days.
MTBF
Mean time between failures: how long things usually run before breaking.
MTTR
Mean time to recovery: how long it takes to get back up once broken.
Single point of failure
A component whose failure stops the whole system.
Redundancy
Extra copies of a component so one can fail without an outage.
SLI, SLO, SLA
What you measure, the target you set for it, and the contract you sign with customers.
Formula  availability = MTBF ÷ (MTBF + MTTR)
Example  fails once a month (720 h), 1 h to recover   → 720 ÷ 721 = 99.86%
Faster   same failures, recover in 6 minutes          → 720 ÷ 720.1 = 99.99%

Recovering faster buys nines as surely as failing less often, and it is usually cheaper: good alerts, quick rollbacks and automatic failover.

Downtime allowed per year

Log scale: each nine is ten times less downtime. Five nines leaves about five minutes a year, less than one slow deploy. Hover a bar for the monthly budget.

Simulator: chains and copies

Copies of each component
Each stage
0
Whole system
0
Downtime per year
0

Components in a chain multiply: all must be up. Copies in parallel fail only if every copy fails at once. This assumes independent failures; shared power, bad deploys and bugs often break copies together, so real gains are smaller.

I.5Intermediate

Load, utilization and queueing

A server at 50% utilization feels fast; at 95% the same server feels broken. Queueing explains why, and Little’s law turns traffic into the number of requests in flight.

Arrival rate (λ)
How many requests arrive per second.
Service time (S)
How long one request takes when nothing else is waiting.
Utilization (ρ)
The share of time a resource is busy: arrival rate × service time, for one server.
Little’s law
Requests in the system = arrival rate × time each one spends there. L = λ × W, true for any stable system.
Saturation
Work arrives faster than it can be served; queues and latency grow without limit.
Headroom
Spare capacity kept free for spikes and failures, often 30 to 40% at peak.

Simulator: the hockey stick

Average response time
0
Slower than an idle server
0
Requests in the system
0

One server, random arrivals, 10 ms of work per request: response time = S ÷ (1 − ρ). Real systems differ in detail but share the hockey stick, which is why capacity is planned for about 60 to 70% utilization at peak.

Calculator: Little’s law

Requests in flight
400

I.6Basic

Data models and storage

Every database is built around one shape of data and a few access patterns. Pick the shape that matches how you read, and the rest of the design gets easier.

Transaction
A group of reads and writes that succeed or fail as one unit.
ACID
Atomic (all or nothing), consistent (rules hold), isolated (as if one at a time), durable (survives crashes).
Index
A sorted structure, usually a B-tree, that finds rows without scanning the table. Speeds reads, slows writes.
OLTP and OLAP
Many small transactional reads and writes, versus large analytical scans over history.
Schema
The declared structure of the data. Enforced on write in relational databases, interpreted on read in many NoSQL stores.
Write-ahead log
Changes are appended to a log before being applied, so a crash can be replayed. Replication and change data capture read the same log.

The same customer and orders, six ways


      
Shape
Good at
Weak at
Examples

Rows or columns on disk

To sum one column, a row store reads every row; a column store reads one contiguous run. That is why analytics warehouses such as BigQuery, Redshift and ClickHouse store columns, which also compress far better, while transactional databases store rows so one order can be read or updated in a single place.
Storage typeWhat it isUse it forExamples
BlockA raw disk volume attached to one machineDatabase files, low latency random reads and writesAWS EBS, local SSD
FileFolders and files shared over the networkShared home directories, legacy appsNFS, AWS EFS
ObjectA flat namespace of immutable blobs fetched by key over HTTPImages, video, backups, data lakes; cheap and very durableAmazon S3, Google Cloud Storage
I.7Basic

Communication patterns

How two parties exchange messages decides latency, server load and what happens when one side is slow. Five patterns cover nearly every design.

Synchronous
The caller waits for the answer. Simple, but the caller is only as fast and available as whatever it calls.
Asynchronous
The caller hands off work, often through a queue, and moves on. Decouples the two sides at the cost of eventual results.
Pull and push
Pull: the client asks for news. Push: the server sends it when it happens.
Webhook
An HTTP callback: you give a service a URL and it calls you when an event happens.
RPC
Calling a function on another machine as if it were local, as gRPC does with typed contracts.
Fan-out
One request or event that triggers many downstream calls or messages.

Five ways to get news from a server

How it behaves

Use it for, and what it costs

StyleFormatStrengthTypical place
RESTJSON over HTTP, resources and verbsUniversal, cacheable, easy to debugPublic and mobile APIs
gRPCProtobuf over HTTP/2Typed contracts, streaming, compact and fastService to service
GraphQLOne endpoint, client picks fieldsNo over-fetching, one round trip for nested dataApps with many different screens
Message queue or logMessages on a brokerDecoupling, buffering, replayBackground work, events between services
I.8Basic

Units, sizes and transfer times

Most estimation mistakes are unit mistakes: bits for bytes, or a thousand for 1,024. These are the numbers worth knowing by heart.

Bit and byte
A byte is 8 bits. Network speeds are quoted in bits (Mbps), file sizes in bytes (MB), so divide by 8.
KB and KiB
KB is 1,000 bytes; KiB is 1,024. For estimates the 2.4% difference does not matter; for disk sizes it adds up.
Throughput in practice
Protocol overhead and congestion usually leave 70 to 90% of the link rate for your data.

Powers of two

PowerValueRemember it as
2101,024about a thousand (KiB)
21665,536ports, 16 bit IDs
220≈ 1.05Mabout a million (MiB)
230≈ 1.07Babout a billion (GiB)
232≈ 4.3Ball IPv4 addresses, 32 bit IDs
240≈ 1.1Tabout a trillion (TiB)
264≈ 1.8 × 101964 bit IDs never run out

Sizes to remember

ThingSize
ASCII character1 B
Integer or timestamp4 or 8 B
UUID16 B
Short post with metadata≈ 1 KB
Phone photo, compressed2 to 5 MB
1 minute of 1080p video at 5 Mbps≈ 37.5 MB
1 hour of 4K video at 15 Mbps≈ 6.8 GB

Calculator: how long does it take to move?

Part II

Method

A repeatable way to attack any prompt, and the numbers you need to reason about scale out loud.

II.1Basic

The 45 minute plan

A system design interview is a conversation about tradeoffs under a clock. The same five moves work for every prompt in this guide, and each case study in Part V follows them in order.

Widths are to scale. Candidates who struggle usually spend 25 minutes drawing boxes in step 3 and never reach the deep dive, which is where most of the senior level signal comes from.
  1. 1

    Clarify requirements

    Agree on users, features, scale and what matters most. Leave with 3 to 5 functional and 3 to 5 non-functional requirements written down.

    “Read heavy or write heavy? Is a few seconds of staleness acceptable?”

  2. 2

    Estimate

    QPS, peak QPS, storage and bandwidth, rounded hard. You want the order of magnitude that decides the design, not precision.

    “35K reads per second is more than one database should take, so we need a cache.”

  3. 3

    High level design

    API, data model, and one diagram of the request path. Get the interviewer to agree before you add detail.

    “That’s the happy path end to end. Which part should we go deeper on?”

  4. 4

    Deep dive

    Two or three hard parts: the hot path, the data that grows, the failure that hurts. Show options, pick one, and say what it costs.

    “Push fan-out breaks for celebrities, so I’d switch them to pull.”

  5. 5

    Wrap up

    Recap, name the bottlenecks, what you would monitor, and what changes at 10× traffic.

    “At 10× the fan-out workers go first. I’d alert on queue lag.”

Mistakes that cost the most

  • Drawing boxes before the requirements are agreed.
  • Naming a product instead of the property you need. Say “a durable, ordered, replayable log” first, then “Kafka”.
  • Presenting one option. Interviewers want the alternative you rejected and why.
  • Ignoring failure. For every box, know what happens when it dies or a call to it times out.
  • Designing for planet scale when the numbers say 50 requests per second.
II.2Basic

Back of the envelope math

Estimates exist to answer one question: does this fit on one machine, or do we need a cache, replicas, partitions or a CDN? Round aggressively and say your assumptions out loud.

1 day
86,400 s ≈ 105 s
1 month
≈ 2.6M s
1M requests / day
≈ 12 per second
Peak traffic
≈ 2 to 3× average
KB, MB, GB, TB, PB
103, 106, 109, 1012, 1015 B
ID, timestamp
8 B each

Estimator

Preset
Write QPS
0
Read QPS
0
Peak read QPS
0
New data per day
0
Total storage
0
Write bandwidth
0

What these numbers imply

    For full worked examples, see how five guides estimate the URL shortener, with a calculator preloaded with each guide’s assumptions.

    Thresholds are rules of thumb for reasoning out loud: a tuned relational database handles a few thousand writes and tens of thousands of simple reads per second; one Redis node handles roughly 100K simple operations per second.

    II.3Basic

    Latency numbers

    Design decisions come from ratios. Memory is about 160 times faster than a random SSD read, and a request across an ocean costs as much time as a million memory lookups.

    CPU and memory Storage Network

    Log scale: each gridline is 10× the one before. Values are rounded orders of magnitude for 2020s hardware, not benchmarks.

    What to take from it

    • Avoid chatty calls. One round trip inside a data center (about 0.5 ms) costs as much as 5,000 memory reads. Batch requests and fetch in bulk.
    • Keep hot data in memory. That is the whole case for caches like Redis.
    • Read sequentially. Reading 1 MB in sequence from SSD is cheaper than 4 random 4 KB reads combined. Log structured storage (Kafka, LSM trees) exploits this.
    • Put data near users. Crossing an ocean costs about 150 ms per round trip, which is why CDNs and multi-region deployments exist.
    • Compress before sending. Compressing 1 KB takes microseconds; sending it across a WAN takes milliseconds.
    Part III

    Scaling

    Every large system started as one box. Each step below exists because a specific limit was hit.

    III.1Basic

    One server to millions of users

    Step through nine changes that take a web app from prototype to a system serving millions. Each step fixes one bottleneck and introduces a new cost. Naming both is exactly what interviewers listen for.

    How to read every diagram in this guide. Shapes say what a component is. Blue marks what the current step adds, red marks the path under discussion, dashed arrows are asynchronous.
    Step 0 of 8

    One server does everything

    Why this step

    What it costs

    Tip: the left and right arrow keys step through the diagram while the buttons have focus.

    Part IV

    Building blocks

    The six components that appear in nearly every design. Learn what each one buys you and what it breaks.

    IV.1Basic

    Load balancing

    A load balancer takes every request on one public address and forwards it to a healthy server behind it. It gives you horizontal scaling (add servers) and failover (stop routing to a server that fails its health check).

    Layer 4 or layer 7

    Layer 4 balancers route TCP or UDP connections by IP and port. Very fast and protocol agnostic, but blind to URLs and headers.

    Layer 7 balancers speak HTTP. They route /api and /images to different pools, terminate TLS and retry safe requests. Examples: NGINX, Envoy, HAProxy, AWS ALB.

    Keeping it reliable

    Health checks call an endpoint such as /healthz every few seconds and take failing servers out of rotation.

    The balancer must not become the new single point of failure. Run an active and standby pair that share a floating IP, or several balancers behind DNS.

    AlgorithmHow it picksGood forWatch out for
    Round robinNext server in orderIdentical servers, short uniform requestsIgnores load, so slow servers pile up work
    Weighted round robinRound robin, bigger servers get more turnsMixed hardwareWeights are static
    Least connectionsServer with the fewest open requestsLong or uneven requests: uploads, WebSocketsBalancer nodes need to share counts
    Power of two choicesPick two at random, use the less loadedLarge fleets with many balancersNear optimal, rarely a problem
    Hash of IP or keySame client or key always hits the same serverCaches, sticky sessionsSkewed keys; use consistent hashing so resizing moves few keys

    Simulator: four servers, two new requests every tick

    IV.2Basic

    Caching

    A cache keeps a copy of hot data in memory so most reads skip the slow path. It works because real traffic is skewed: a small share of keys receives most of the requests.

    Caches exist at every layer: the browser, the CDN, the API gateway, an application cache such as Redis or Memcached, and the database’s own buffer pool. In interviews “add a cache” usually means an application cache, and the follow up is always the same: how does data get in, and how does stale data get out?

    Eviction policies

    • LRU evicts the least recently used key. The default almost everywhere.
    • LFU evicts the least frequently used. Better when popularity is stable.
    • TTL expires keys after a fixed time. Your safety net against stale data.

    Failure modes to name

    • Stampede. A hot key expires and thousands of requests hit the database at once. Let one request refresh while others wait, and add random jitter to TTLs.
    • Hot key. One key overloads one cache node. Replicate it under suffixes (key#1 to key#8) or keep a small local cache per server.
    • Stale reads. Update the database first, then delete the cache key.

    Simulator: an LRU cache

    Request key
    Most recentEvicted next

    Hits
    0
    Misses
    0
    Hit ratio
    0%

    Skewed traffic sends half the requests to keys A and B, the way real users hammer a few popular items. Compare its hit ratio with uniform traffic across eight keys: the same small cache is far more useful when access is skewed.

    IV.3Intermediate

    Consistent hashing

    When data is spread over N servers, you need a rule that maps each key to a server. The obvious rule, hash(key) mod N, remaps almost every key when N changes. For a cache cluster that means a sudden flood of misses; for a database, a huge migration.

    The ring

    Hash servers and keys onto the same circle of values. A key belongs to the first server found moving clockwise. Adding a server only takes keys from one neighbor; removing one hands its keys to the next. On average only 1/N of the keys move.

    Virtual nodes

    With one position per server the arcs are uneven, so some servers own far more keys. Give each server many positions (virtual nodes) and the load evens out; when a server leaves, its keys spread across all the others. Used by Dynamo, Cassandra, Riak and most sharded caches.

    Positions per server
    Keys moved on the ring
    None yet
    With hash mod N
    None yet

    Keys per server (dashed line is a perfectly even share)

    Add a server and compare the two counters. Then switch to 64 virtual positions per server and watch the bars even out. You can also click any key dot on the ring.

    IV.4Intermediate

    CAP and consistency

    When a network partition splits a replicated system, each side must choose: refuse requests it cannot answer correctly (consistency) or answer with what it has (availability). Partitions happen in every real network, so the practical question is which one you give up while it lasts.

    Choose consistency (CP) when

    A wrong answer is worse than no answer: account balances, inventory reservations, leader election, unique usernames. Examples: ZooKeeper, etcd, Spanner, a single primary relational database.

    Choose availability (AP) when

    A slightly old answer is fine and downtime is not: shopping carts, like counts, feeds, DNS. Examples: Cassandra and DynamoDB in their default modes.

    PACELC adds the everyday case: even with no partition, waiting for replicas to confirm (consistency) costs latency. Most tuning happens here, not during rare partitions.

    Simulator: two replicas and a network cable

      Try it: cut the network, write, then read, in both modes. Then heal the network and see what an AP system has to reconcile.

      Consistency models, strongest to weakest

      ModelGuaranteeExample use
      Strong (linearizable)Every read sees the latest completed writePayments, locks, counters that must not go negative
      Read your writesYou always see your own updates; others may lagEditing your profile, posting a comment
      Monotonic readsYou never see time go backwards across readsPinning a user to one replica
      EventualReplicas converge if writes stopLike counts, view counters, DNS
      IV.5Intermediate

      Queues and streams

      A queue sits between a producer and a consumer so neither waits for the other. It absorbs bursts, lets each side scale on its own, and keeps work safe when a consumer crashes.

      Two shapes

      Message queue (RabbitMQ, SQS): each message goes to one consumer and is deleted once acknowledged. Good for jobs.

      Log or stream (Kafka, Kinesis, Pulsar): messages are kept for days in ordered partitions. Many consumer groups read at their own offset and can replay history. Good for events that several systems need.

      Delivery guarantees

      At most once may lose messages. At least once may deliver duplicates and is the usual default. Exactly once in practice means at least once delivery plus an idempotent consumer that ignores message IDs it has already processed.

      Ordering holds only within a partition, so partition by the key whose order matters, such as order_id. After N failed attempts, park a message in a dead letter queue instead of blocking the partition.

      Simulator: a traffic spike hits the queue

      Peak backlog
      0
      Longest wait
      0 s
      Backlog cleared
      n/a

      IV.6Intermediate

      Rate limiting

      A rate limiter caps how many requests a client can make in a window. It protects services from abuse and from well meaning clients stuck in retry loops, and it keeps costs predictable.

      Simulator: token bucket

      Allowed (200)
      0
      Rejected (429)
      0
      Retry-After hint
      0 s

      Each request spends one token; tokens drip back at the refill rate. A full bucket lets a burst through, then the limiter settles to the refill rate. That is why Stripe and AWS use this algorithm: it allows short bursts while enforcing an average.

      AlgorithmHow it worksStrengthWeakness
      Token bucketTokens refill at rate r up to capacity b; each request takes oneAllows bursts, tiny memoryTwo parameters to tune
      Leaky bucketRequests queue and drain at a fixed rateSmooth, steady outputBursts wait or are dropped
      Fixed windowCounter per minute, reset at the boundarySimplest to build2× the limit can pass around a boundary
      Sliding window logStore each request timestamp, count the last 60 sExactMemory grows with traffic
      Sliding window counterBlend current and previous window counts by overlapAccurate enough, cheapApproximate

      Where it runs

      Put the limiter in the API gateway or as middleware, keyed by user ID, API key or IP. Counters live in Redis so every gateway node sees the same numbers; wrap check and decrement in a Lua script (or use INCR with EXPIRE) so two nodes cannot both spend the last token. Rejected calls get 429 Too Many Requests with a Retry-After header, and well behaved clients back off exponentially.

      Part V

      Case studies

      Fourteen prompts that come up again and again, in order of difficulty. Each opens with a short video of the system at work, then follows the 45 minute plan: requirements, estimates, API and data model, the design, and the deep dives interviewers push on.

      V.0Mixed

      Use case map

      The design prompts that come up again and again, what each one is really testing, and where to practice it here. Most prompts are a new costume on a handful of core problems.

      Show
      PromptLevelThe core problemKey techniquesPractice it
      URL shortenerBasicRead heavy lookups by keyBase62 IDs, cache, 302 redirectsV.1
      Rate limiterBasicShared counters at low latencyToken bucket, Redis with atomic scriptsIV.6
      Unique ID generatorBasicOrdering without coordinationSnowflake IDs, time sortable keysV.3
      News feedIntermediateFan-out costPush, pull and hybrid; feed cache of IDsV.2
      Chat or messagingIntermediateStateful connections, ordering, offline usersWebSockets, inbox queues, presenceV.3
      Notification systemIntermediateMany channels, flaky providersPer channel queues, dedupe, retries, dead lettersV.4
      Search autocompleteIntermediatePrefix lookups in millisecondsTrie with top K per node, offline rebuildV.5
      Web crawlerIntermediatePoliteness and dedupe at scaleURL frontier, Bloom filter, SimHashV.6
      Nearby placesIntermediateTwo dimensional searchGeohash, quadtree, nine cell queryV.7
      Distributed cacheIntermediateHot keys and resizingConsistent hashing, LRU, replicationIV.2, IV.3
      Message queueIntermediateDurability with orderingPartitioned log, offsets, consumer groupsIV.5
      Leaderboard or top KIntermediateRanking a changing setRedis sorted sets; count-min sketch for streamsVI.1
      Ticket or hotel bookingAdvancedContention on scarce inventoryConditional updates, holds with expiry, waiting roomV.8
      Ride sharingAdvancedLocation firehose plus exclusive matchingIn memory geo index, compare and setV.9
      Video streamingAdvancedBandwidth and transcodingSegment DAG, adaptive bitrate, CDNV.10
      File syncAdvancedSend only what changed, never lose dataContent hashed blocks, versioned commitsV.11
      Collaborative editorAdvancedConcurrent edits must convergeOT or CRDT, one server per documentV.12
      Key value storeAdvancedPartitioning with high availabilityConsistent hashing, quorums, vector clocksV.13
      Payment systemAdvancedCorrectness over unreliable networksIdempotency keys, ledger, reconciliationV.14
      Metrics and monitoringAdvancedHuge volumes of time series writesTime series storage, downsampling, push or pull collectionVI.1
      Ad click aggregationAdvancedExact counts in near real timeStream processing with windows, idempotent sinks, reconciliationIV.5

      Spot the families: booking, payments and ride matching are all contention problems; feeds, chat and notifications are fan-out problems; autocomplete, crawlers and nearby search are indexing problems.

      V.1Basic

      URL shortener

      The classic warm up. A small surface area that still tests estimation, ID generation, caching and the difference between a 301 and a 302.

      Interview prompt

      Design a service like TinyURL: given a long URL, return a short one that redirects to it.

      Functional requirements

      • Create a short URL for a long one, with optional custom alias and expiry.
      • Visiting the short URL redirects to the long one.
      • Optional: click counts per link.

      Non-functional requirements

      • Redirects are fast, a few milliseconds at the server.
      • Highly available: a dead short link is a broken page on someone else’s site.
      • Codes are never reused and are hard to enumerate.

      Estimates

      New URLs    100M per month ÷ 2.6M s        ≈ 40 writes/s
      Redirects   100 reads per write             ≈ 4,000 reads/s, peak ≈ 8,000
      Total URLs  100M × 12 months × 10 years     = 12B
      Storage     12B × 500 B                     ≈ 6 TB
      Code space  62⁶ ≈ 56.8B, 62⁷ ≈ 3.5T         → 7 characters gives plenty of headroom

      Calculator: run the numbers yourself

      Writes per second
      Redirects per second
      Peak redirects per second
      Storage
      Shortest code
      Cache, 80/20 method

      What the numbers decide

        Numbers used in these calculations

        NumberValueUsed for
        Seconds in a day, a month86,400 ≈ 105; ≈ 2.5MTurning daily or monthly volume into per second rates
        Memory read≈ 100 nsWhy hot redirects are served from a cache
        SSD read, HDD read≈ 0.1 ms, ≈ 10 msMemory is about 1,000× faster than SSD
        SSD random reads≈ 100,000 IOPSPeak reads beyond this cannot come from one disk
        HDD random reads100 to 200 IOPSWhy nobody serves hot reads from spinning disks
        One Redis instance100,000+ ops/sCache nodes needed at peak; the global ID counter
        Network latency: same AZ, across AZs, across regions< 1 ms; 1 to 2 ms; 50 to 150 msThe 100 ms redirect budget; why a CDN helps distant users
        Network bandwidth per instance25 Gbps standard, 50 to 100 high endBandwidth is never the limit for 500 byte rows
        One large machine512 GiB RAM, 128 vCPUs; up to 24 TB RAM; up to 60 TB local SSDWhether data and cache fit on one box

        The point of these numbers is that modern machines are huge. Rules like “split the database at 100 GB” are long outdated, and sharding before the numbers demand it is a common interview mistake. At 500 GB, a URL shortener fits comfortably on one Postgres server; replicate it for availability, and shard only when throughput or size truly requires it.

        API

        POST /api/v1/urls
        { "longUrl": "https://example.com/a/long/path",
          "alias": null, "expiresAt": null }
        → 201 { "shortUrl": "https://sho.rt/zn9edcu" }
        
        GET /zn9edcu
        → 302 Found
          Location: https://example.com/a/long/path

        Data model

        urls
          code        CHAR(7)    PRIMARY KEY
          long_url    TEXT
          owner_id    BIGINT
          created_at  TIMESTAMP
          expires_at  TIMESTAMP  NULL

        Every read is a lookup by primary key, so a key value store (DynamoDB, Cassandra) or a relational table both work.

        Deep dive: generating the short code

        ApproachHowProsCons
        Hash and truncateHash the long URL (MD5, SHA-256), keep the first 7 base62 charactersSame URL gives the same code; no coordinationCollisions: check the store, rehash with a salt; an extra read on every write
        Base62 of a unique ID Common pickEach server leases a block of IDs (say 1,000) from a coordinator, encodes each ID in base62No collisions, no extra reads, simpleSequential codes are guessable unless you shuffle the bits
        Key generation servicePre-generate random codes offline; servers claim batchesRandom and collision freeOne more service; claimed keys are lost if a server crashes

        Lab: shorten a URL both ways

        Base62 of the ID

        Number÷ 62RemainderChar

        Divide by 62 until nothing is left, then read the remainders from the bottom up. Alphabet: 0 to 9, a to z, A to Z.

        Hash and truncate

        Deep dive: 301 or 302

        A 301 Moved Permanently is cached by the browser, so repeat visits never reach you. Less load, but you lose click analytics and cannot change or expire the link for that visitor. A 302 Found sends every click through your servers: more load, complete analytics. Most commercial shorteners use 302 because analytics is the product.

        Deep dive: scaling reads

        Redirects outnumber writes 100 to 1, and clicks concentrate on recent and viral links. Cache code → long URL in Redis with LRU eviction. If the hottest 20% of a month’s links take most of the clicks, that is 20M × 500 B = 10 GB, which fits in one Redis node. Add read replicas behind the cache, and for extreme traffic answer redirects at the CDN edge.

        What the interviewer probes next

        How do custom aliases work?

        Same table, same unique key. Check availability on create and reserve words like api and login. To keep aliases from colliding with generated codes, generated codes are always exactly 7 characters and aliases must be another length, or both go through the same uniqueness check.

        How do expired links get cleaned up?

        Lazily on read (return 410 Gone when expired) plus a low priority job that deletes expired rows in batches. Never recycle codes: an old link printed in a book would suddenly point at a stranger’s page.

        How do you count clicks without slowing redirects?

        Redirect first, then publish a click event to a queue such as Kafka. Stream processors aggregate counts per link per minute into an analytics store. The redirect never waits on analytics.

        How do you stop abuse?

        Rate limit creation per user and IP, check new URLs against malware and phishing lists such as Google Safe Browsing, and require an account for high volume.

        V.2Intermediate

        News feed

        The central question is when to do the work: when a post is written, or when a feed is read. The answer changes with follower count, which is why every real feed is a hybrid.

        Interview prompt

        Design the home timeline for a social network: users follow others and see their posts, newest first.

        Functional requirements

        • Publish a post with text and media.
        • Follow and unfollow users.
        • View a paginated feed of posts from followed users.

        Non-functional requirements

        • Feed loads in under 200 ms.
        • A new post may take a few seconds to appear (eventual consistency).
        • Read heavy and highly available.

        Estimates

        Feed reads  300M DAU × 10 opens ÷ 86,400 s     ≈ 35,000 reads/s
        New posts   300M DAU × 2 posts ÷ 86,400 s      ≈ 7,000 posts/s
        Fan-out     7,000 posts/s × 200 followers      ≈ 1.4M feed inserts/s if we push
        Feed cache  300M users × 500 post IDs × 8 B    ≈ 1.2 TB of IDs in memory

        API

        POST /v1/posts
        { "text": "...", "mediaIds": ["m_81"] }
        
        GET /v1/feed?limit=20&cursor=1783229482342
        → 200 { "posts": [...],
                "nextCursor": "1783228190031" }

        Data model

        posts    post_id PK, author_id, text,
                 media_ids, created_at
        follows  follower_id, followee_id
        feeds    user_id → [post_id, ...]
                 Redis list, capped at ~500
        Two paths. Publishing writes the post, then fan-out workers look up followers and push the post ID into each follower’s feed cache. Reading takes the list of IDs from the feed cache and hydrates them from the post and user caches. Feeds hold only IDs, so edits and deletions show up without rewriting feeds.

        Deep dive: push or pull

        Where the arrows multiply. Push does one write per follower when a post is created, so reading is a single lookup. Pull writes once, but every feed open queries every account the reader follows and merges the results.

        Calculator: what one post and one feed open cost

        Push (on write)Pull (on read)Hybrid
        Writes when this author posts
        Lookups when a reader opens the feed
        Time until every follower can see it (1M inserts/s)

        Hybrid is the standard answer: push for ordinary accounts, and for accounts above a threshold (say 100K followers) skip fan-out. At read time the feed service merges the precomputed feed with fresh posts from the few celebrities the reader follows. Skip fan-out to users inactive for 30 days too, and build their feed by pull when they come back.

        Deep dive: pagination and ranking

        Paginate with a cursor (the last post ID seen), never with OFFSET. New posts arriving at the top shift every offset, so page two by offset repeats items; a cursor stays stable. Ranked feeds fetch a few hundred candidates, score them by affinity, recency and engagement, and cache the ranked page for a few minutes.

        What the interviewer probes next

        A user follows someone new. When do old posts appear?

        Backfill asynchronously: fetch the new followee’s recent posts and merge them into the follower’s cached feed. Unfollow is handled by filtering at read time and cleaning the cache lazily.

        The feed cache node dies. Now what?

        The feed cache is derived data. Rebuild a user’s feed on demand by pulling recent posts from their followees, then resume pushing. Replicate the cache so a single node loss rarely forces this.

        How are deleted or edited posts handled?

        Feeds store IDs only. Hydration reads the current post, and deleted posts are filtered out at read time, so nothing needs to be rewritten across millions of feeds.

        How do media uploads fit in?

        Clients upload directly to object storage with a pre-signed URL, a worker creates thumbnails, and the CDN serves them. Posts keep only media IDs.

        V.3Intermediate

        Chat system

        Chat introduces stateful servers: millions of open connections that must find each other. The design turns on how a message travels from one socket to another, and what happens when the other side is offline.

        Interview prompt

        Design a chat app like WhatsApp or Messenger: one to one and small group chats, online status, and sync across devices.

        Functional requirements

        • One to one and group chats of up to 100 members.
        • Delivery and read receipts, online presence.
        • Offline users get push notifications and catch up on reconnect.
        • History syncs across a user’s devices.

        Non-functional requirements

        • Low delivery latency.
        • Messages ordered within a conversation.
        • Durable: an acknowledged message is never lost.

        Estimates

        Messages     50M DAU × 40 per day ÷ 86,400 s    ≈ 23,000 messages/s
        Storage      2B messages/day × 100 B            ≈ 200 GB/day, ≈ 73 TB/year
        Connections  10M concurrent ÷ 100K per server   ≈ 100 chat servers

        How clients stay connected

        TechniqueHow it worksTradeoff
        Short pollingClient asks “anything new?” every few secondsSimple, but most responses are empty and waste capacity
        Long pollingServer holds the request open until a message arrives or a timeoutFewer empty replies; still one request per message and constant reconnects
        WebSocket PickOne persistent, two way connection over TCPReal time both ways; servers become stateful and need reconnect handling

        Use WebSockets for messages only. Login, profiles, group management and media uploads stay on ordinary stateless HTTP services behind a load balancer.

        Step through: Alice sends Bob a message

        Deep dive: ordering with Snowflake IDs

        Client clocks drift, so ordering comes from IDs the server assigns. A Snowflake ID packs a timestamp into the high bits, so sorting by ID sorts by time, and each generator can mint thousands of IDs per millisecond with no coordination.

        • Timestamp, 41 bits: milliseconds since a custom epoch, enough for about 69 years.
        • Machine, 10 bits: up to 1,024 generators.
        • Sequence, 12 bits: 4,096 IDs per millisecond per generator, reset every millisecond.
        ID
        n/a
        Time
        n/a
        Machine
        n/a
        Sequence
        n/a

        Deep dive: storage, presence, groups

        Message store

        Chat is write heavy and reads are mostly recent. A wide column store (Cassandra, HBase, ScyllaDB) partitioned by conversation_id and sorted by message_id serves “latest 50 messages” from one partition. Discord runs this model on ScyllaDB, and Facebook Messenger ran it on HBase for years.

        Multi-device sync

        Each device keeps a cursor: the largest message ID it has. On reconnect it asks for everything after that ID, which also covers offline delivery.

        Presence

        Clients send a heartbeat every 5 seconds. A user is marked offline only after 30 seconds of silence, so a train through a tunnel does not flicker their status. Changes are published to friends over pub/sub; in large groups, presence is fetched only when someone opens the member list.

        Groups

        Small groups copy each message into every member’s inbox queue. Large channels store once and let members pull, like celebrities in the news feed.

        What the interviewer probes next

        A chat server crashes. What happens to its users?

        Their sockets drop, and clients reconnect with backoff and jitter; service discovery (ZooKeeper, etcd) hands them another server. Messages were persisted before delivery, and clients resend anything unacknowledged with the same client message ID, which the server deduplicates.

        Two people in a group send at the same moment. Who comes first?

        Whoever the server assigns the lower ID. Using one ID source per conversation gives every member the same order; device timestamps are only for display.

        How does end to end encryption change the design?

        Keys live only on devices (the Signal protocol), so servers store and forward ciphertext. Server side search and message previews in push notifications no longer work unless the device decrypts them locally.

        How do you deploy without dropping 10M connections?

        Drain servers one at a time: stop accepting new sockets, ask connected clients to reconnect gradually, then restart. The load balancer must support long lived connections and not cut them on a fixed timeout.

        V.4Intermediate

        Notification system

        Many services, three channels, unreliable third party providers. The design is about keeping channels isolated from each other, retrying without spamming, and respecting what each user asked for.

        Interview prompt

        Design a system that sends push notifications, SMS and email on behalf of every team in the company.

        Functional requirements

        • Send through push (iOS, Android), SMS and email.
        • Respect per user preferences, opt-outs and quiet hours.
        • Immediate, scheduled and bulk campaign sends from templates.
        • Track sent, delivered and opened.

        Non-functional requirements

        • Urgent messages such as login codes arrive within seconds.
        • No lost notifications, and no duplicates the user can see.
        • A slow provider in one channel never delays the others.

        Estimates

        Daily volume  10M push + 1M SMS + 5M email       = 16M per day, ≈ 185/s
        Campaign peak 10M pushes in 10 minutes           ≈ 17,000/s
        Log storage   16M × 500 B × 365 days             ≈ 2.9 TB per year

        API

        POST /v1/notifications
        { "eventId": "evt_8812", "userId": 42,
          "template": "payment_succeeded",
          "params": { "amount": "€50" },
          "channels": ["push", "email"],
          "priority": "high" }
        → 202 Accepted { "notificationId": "ntf_19a" }

        Data model

        devices        user_id, token, platform
        preferences    user_id, category, channel,
                       enabled, quiet_hours
        notifications  id PK, event_id UNIQUE,
                       user_id, channel, status,
                       sent_at, opened_at

        Deep dive: delivery you can trust

        • Persist first. Write the notification row, then enqueue it. If a worker crashes, a sweeper finds rows stuck in PENDING and re-enqueues them.
        • Deduplicate by event ID. The unique constraint on event_id means a service that publishes the same event twice still produces one notification.
        • One queue per channel, with priority lanes. Login codes and payment alerts never wait behind a ten million user marketing campaign.
        • Retry with exponential backoff and jitter, within each provider’s rate limits. After a fixed number of attempts, move the message to a dead letter queue and alert.
        • Close the loop. Providers report delivery through callbacks; the app reports opens. Both update the status row.
        ChannelProvider examplesWatch out for
        PushAPNs (Apple), FCM (Google)Device tokens go stale. Delete a token when the provider reports the app as unregistered.
        SMSTwilio, VonageCost per message, country specific sender rules, and abuse such as SMS pumping fraud.
        EmailAmazon SES, SendGridBounces and spam complaints damage your sender reputation. Marketing email needs an unsubscribe link.

        Deep dive: not annoying people

        Check preferences at send time, not when the message is queued, because a user may opt out in between. Cap marketing messages per user per day, hold non-urgent messages during quiet hours in the user’s own time zone, and bundle bursts into digests: “12 people liked your post” instead of twelve pings.

        What the interviewer probes next

        How do you send a campaign to 10M users without breaking anything?

        A scheduler expands the audience in batches of a few thousand and feeds the low priority lane. Workers throttle to each provider’s quota. Transactional messages use their own lane and workers, so they are unaffected.

        A login code must arrive fast. How?

        A dedicated high priority queue with reserved workers, a short expiry (a code that arrives after it expires is useless), and a fallback from push to SMS if no delivery receipt arrives within a few seconds.

        The same event is published twice. Does the user get two pushes?

        No. The second insert violates the unique event_id and is dropped. As a second guard, the app ignores notification IDs it has already shown.

        How do you know a notification was opened?

        The app reports opens with the notification ID; emails use a tracking pixel and wrapped links. Events stream into analytics and update the status row.

        V.5Intermediate

        Search autocomplete

        Every keystroke is a request, which makes this one of the busiest endpoints a company runs. The trick is to do the expensive ranking offline and make serving a plain memory lookup.

        Interview prompt

        Design the suggestions that appear under a search box as the user types.

        Functional requirements

        • Return the top 5 completions for a prefix, ranked by popularity.
        • Trending searches appear within about an hour.
        • Offensive and blocked terms never appear.

        Non-functional requirements

        • Under 100 ms end to end, so the server budget is about 20 ms.
        • Highly available; if suggestions fail, search still works.
        • Freshness of minutes to hours is fine.

        Estimates

        Requests     10M DAU × 10 searches × 20 keystrokes = 2B per day, ≈ 23,000/s
        Peak         ≈ 2×                                   ≈ 50,000/s
        Prefix index ≈ 1B prefixes × 5 IDs × 8 B         ≈ 40 GB plus keys, so shard it

        API

        GET /v1/suggest?q=tw&lang=en
        → 200 Cache-Control: max-age=3600
          { "suggestions": ["twitter", "twitch",
            "twilight", "twins", "tweetdeck"] }

        What each trie node stores

        prefix  top 5 completions, precomputed
        t       twitter, twitch, tiktok, target, translate
        tw      twitter, twitch, twilight, twins, tweetdeck
        twi     twitch, twilight, twins, twitter, twine

        Storing the top five at every node is what makes serving fast: a lookup walks down as many nodes as the prefix has characters and returns the stored list, with no need to search the subtree below.

        Deep dive: where suggestions live

        ApproachServingUpdatingFits when
        Trie in memory, top 5 per node Common pickWalk the prefix, return the listRebuilt offline and swapped inThe classic interview answer
        Prefix → top 5 in a key value storeOne GET per keystrokeBatch write every prefixSimple to shard and cache, more storage
        Search engine completion suggesterIn memory automaton with fuzzy matchingNear real time indexingYou already run Elasticsearch or OpenSearch and need typo tolerance

        Deep dive: the data pipeline

        Search logs flow into a stream. A batch job counts queries over the last few weeks with older counts weighted less; a streaming job counts the last hour to catch trending terms. The scores are merged, a builder produces a new trie, writes it to object storage, and serving nodes load it and swap it in atomically. Reads never wait for writes.

        Deep dive: cutting the load

        • Debounce in the client: send only after a short pause in typing.
        • Cache responses in the browser, and at the CDN for short prefixes, which are the hottest and identical for everyone.
        • Shard by prefix range using a shard map balanced by traffic, because far more queries start with “s” than with “x”.

        What the interviewer probes next

        A harmful term starts trending. How do you remove it right now?

        Filter results against a blocklist at serving time, before returning them. The next rebuild removes the term from the trie itself.

        How would you personalize suggestions?

        Blend the global top results with the user’s own recent searches, kept on the device or in a small per user store, and merge them in the client or at the edge.

        How do you handle typos?

        Fuzzy matching by edit distance. It is much more expensive, so run it only when the exact prefix returns few results; completion suggesters in search engines support it directly.

        What about other languages?

        Build one trie per language or region, normalize Unicode, and for languages typed through an input method, such as Chinese pinyin, index the typed form as well as the final characters.

        V.6Intermediate

        Web crawler

        A crawler is a loop: take a URL, fetch it, extract its links, repeat. At a billion pages the hard parts are staying polite to every website, never fetching the same thing twice, and surviving the strange corners of the web.

        Interview prompt

        Design a web crawler that collects a billion pages a month for a search engine.

        Functional requirements

        • Start from seed URLs and follow links.
        • Store page content for indexing.
        • Recrawl pages according to how often they change.
        • Obey robots.txt.

        Non-functional requirements

        • Politeness: never overload any single website.
        • Scales to hundreds of fetcher machines.
        • Robust to broken HTML, slow servers and crawler traps.
        • Extensible to new content types such as PDFs.

        Estimates

        Fetch rate  1B pages ÷ 2.6M s per month         ≈ 385 pages/s, peak ≈ 800
        Storage     1B × 500 KB                         = 500 TB per month, ≈ 30 PB in 5 years
        URL seen    10B URLs in a Bloom filter at 1%    ≈ 12 GB of memory

        Deep dive: the URL frontier

        Front queues decide what matters, back queues decide when. The prioritizer ranks URLs by importance and how often the page changes. The router keeps one back queue per host, and a fetcher may take that host’s next URL only after its politeness delay has passed, tracked in a heap of next allowed times.

        Deep dive: never twice

        Same URL

        Normalize first: lowercase the host, drop fragments, sort query parameters, strip tracking parameters. Then check a Bloom filter of URLs already seen. A false positive means a page is occasionally skipped, which is acceptable; it never causes a duplicate fetch.

        Same content

        Mirrors and session parameters serve identical pages under many URLs. A checksum of the content catches exact copies; SimHash catches near duplicates, such as the same article with a different ad block.

        TrapWhat happensDefense
        Endless calendars“Next month” links foreverMaximum depth and pages per host
        Session IDs in URLsEvery visit looks like a new pageStrip known parameters, content dedupe
        Generated link farmsPages that link to ever new pagesURL length limits, per host budgets
        Slow serversFetchers sit idle waitingTight timeouts; one host per back queue isolates it

        What the interviewer probes next

        How do you decide when to recrawl a page?

        Estimate each page’s change rate from past crawls: news front pages hourly, documentation monthly. Use conditional requests with If-Modified-Since or ETag, so unchanged pages return a 304 with no body.

        What about pages built with JavaScript?

        Send them to a separate render queue served by headless browsers. Rendering costs far more than fetching, so do it only when the static HTML lacks the content.

        How are hosts split across crawler machines?

        Hash the hostname to a machine with consistent hashing. One machine then owns all politeness state for a host, and adding machines moves only some hosts.

        DNS turns out to be the bottleneck. Why, and what helps?

        Each lookup can take tens to hundreds of milliseconds and many resolvers are single threaded. Run a caching resolver on every crawler machine and respect record TTLs.

        V.7Intermediate

        Nearby places

        A read heavy search problem with a twist: databases index one dimension well, and location has two. The design is mostly about the geospatial index.

        Interview prompt

        Design the backend for “restaurants near me” as in Yelp or Google Maps: given a location and a radius, return nearby businesses.

        Functional requirements

        • Search by location and radius: 0.5, 1, 2, 5 or 20 km.
        • View business details.
        • Owners add and update businesses; changes may appear the next day.

        Non-functional requirements

        • Low search latency.
        • Handles lunch and dinner peaks.
        • User locations are private and not stored longer than needed.

        Estimates

        Searches    100M DAU × 5 searches ÷ 86,400 s     ≈ 5,800 QPS, peak ≈ 15,000
        Businesses  200M × 1 KB                           ≈ 200 GB
        Geo index   200M × (6 B geohash + 8 B ID)         ≈ 3 GB, fits in memory

        Why a plain index fails

        SELECT id FROM business
        WHERE lat BETWEEN 48.853 AND 48.863
          AND lng BETWEEN 2.284 AND 2.305;

        An index on latitude returns a thin strip around the whole planet, and one on longitude returns another. The database has to intersect two huge sets. What we need is a way to map two dimensions to one so that points close on the map stay close in the index.

        Geohash does this. Split the world in half by longitude and write 1 if the point is in the upper half, 0 if lower. Then split by latitude, then longitude again, alternating. Every 5 bits become one base32 character, and each extra character makes the cell 32 times smaller. Nearby points usually share a prefix, so “everything in this cell” becomes a prefix lookup.

        Explorer: encode a location

        Geohash

        Bits, 5 per character (blue longitude, red latitude)

        First splits

        AxisRangeMidpointBit
        Cell size here
        Suggested precision

        The boundary problem

        Two cafés across a street can sit in different cells with no shared prefix. So never search only the user’s cell: query the cell and its eight neighbors, compute exact distances for the candidates, drop those outside the radius and sort. If too few results come back, drop one character of precision and search again.

        IndexIdeaStrengthWeakness
        GeohashFixed grid, cell ID is a string prefixWorks in any database or Redis (GEOSEARCH); simple to shard and cacheFixed cell size regardless of density
        QuadtreeIn memory tree; split any cell holding more than K places into fourAdapts to density: small cells in Manhattan, huge ones in a desertBuilt at startup, harder to update live
        S2 or H3Hierarchical cells on a sphere (S2 squares on a Hilbert curve, Uber’s H3 hexagons)Covers any shape well; good for geofencesMore complex to explain and operate
        Split reads from writes. The location service is stateless and scales out for 15,000 QPS. It looks up the nine cells in the geo cache, falls back to read replicas, then hydrates business details. Owner edits go through a separate low traffic service to the primary; a nightly job rebuilds the index. The 3 GB index is replicated to every read node rather than sharded.

        What the interviewer probes next

        What changes for moving objects, like ride share drivers?

        Locations update every few seconds, so it becomes write heavy. Keep driver positions in memory (Redis geo sets or an in memory grid keyed by cell) with short TTLs, and never write every update to the main database.

        Should we shard the geo index?

        Not by default. At 3 GB it fits in memory on every replica, and sharding by region creates hot shards in big cities. Replicate it and shard only the business table, by business ID.

        How do you cache searches when every user is at a different point?

        Cache by cell, not by coordinates: key is the geohash at the precision for the radius, value is the list of business IDs. Every user in the same cell shares the entry.

        V.8Advanced

        Ticket booking

        Thousands of people want the same few seats in the same second. The design is about contention: exactly one buyer gets each seat, holds expire cleanly, and the on-sale spike never takes the site down.

        Interview prompt

        Design Ticketmaster: users find an event, choose seats and buy tickets.

        Functional requirements

        • Browse events and see a seat map with availability.
        • Hold seats for a few minutes during checkout.
        • Pay and receive tickets.
        • No seat is ever sold twice.

        Non-functional requirements

        • Strong consistency for seat state.
        • Survive on-sale spikes of millions of users.
        • Fair access; bots kept out.
        • Browsing may be a few seconds stale.

        Estimates

        Normal day  1M tickets per day                  ≈ 12 per second
        Big on-sale 2M fans for 50,000 seats            = 40 fans per seat
        Seat map    2M fans refreshing every 5 s         = 400,000 reads/s, so cache and push

        Data model

        events    event_id, venue_id, on_sale_at
        seats     event_id, seat_id, section, price,
                  status FREE | HELD | SOLD,
                  held_by, hold_expires_at, version
        bookings  booking_id, user_id, event_id,
                  seat_ids, payment_id, status

        The one statement that matters

        UPDATE seats
        SET status = 'HELD', held_by = :user,
            hold_expires_at = now() + interval '10 min'
        WHERE event_id = :event AND seat_id = :seat
          AND (status = 'FREE' OR
              (status = 'HELD' AND hold_expires_at < now()));
        /* 1 row changed: you hold it.
           0 rows: someone else does. */

        Deep dive: preventing double booking

        ApproachHowTradeoff
        Pessimistic lockSELECT … FOR UPDATE inside a transactionSimple; contenders queue on the row lock
        Optimistic lockRead version, then UPDATE … WHERE version = :vNo locks; under heavy contention most attempts fail and retry
        Conditional update Common pickOne UPDATE with the status check in WHEREAtomic, one round trip, no retry loop
        Redis hold with expirySET seat:A12 user NX PX 600000Fast holds off the database; the database must still enforce the final sale

        Deep dive: surviving the on-sale

        • Virtual waiting room. Fans get a place in line and a signed admission token; the booking service only accepts requests with a valid token and admits at the rate it can serve.
        • Seat map from cache, changes pushed. Serve the map from a cache refreshed every second or two and push seat changes over server-sent events. The hold step is authoritative, so a slightly stale map is harmless.
        • Split the hot event. One event cannot be sharded by event ID; partition its seats by section instead.

        What the interviewer probes next

        The hold expires while the fan is paying. Now what?

        Confirmation re-checks the hold. Extend it once when payment starts; if it was lost anyway and the charge succeeded, refund automatically and tell the fan clearly.

        Why not keep seat state in application memory?

        There are many application servers and their memory is not shared. One authority, the database or a single Redis key per seat, must arbitrate.

        How do you stop bots from buying everything?

        Verified fan presales, per account and per card limits, a challenge at waiting room entry, and device fingerprinting.

        How does this change for hotels or flights?

        Same core: conditional updates on inventory counts, holds with expiry and idempotent confirmation. Airlines also deliberately overbook, which is a business rule layered on top.

        V.9Advanced

        Ride sharing

        Two hard problems in one: absorbing a firehose of GPS updates, and matching each rider to exactly one nearby driver within seconds.

        Interview prompt

        Design Uber: a rider requests a ride, the system finds a nearby driver, and both follow the trip live on a map.

        Functional requirements

        • Drivers share their location while online.
        • Riders see nearby cars and an arrival estimate.
        • Request, match, accept; live tracking; fare at the end.

        Non-functional requirements

        • Match within a few seconds.
        • Never assign one driver to two riders.
        • Absorb hundreds of thousands of location updates per second.
        • A failure in one region does not affect others.

        Estimates

        Location    1M drivers online × 1 update per 4 s = 250,000 writes/s
        Ingest      250,000 × 100 B                       ≈ 25 MB/s
        Ride asks   10M rides per day                     ≈ 115/s, peak ≈ 600/s

        API

        PUT  /v1/drivers/me/location
             { lat, lng, heading, ts }
        POST /v1/rides { pickup, dropoff }
        → 202 { rideId }
        WS   /v1/rides/{id}/stream
        → driver position, ETA

        Driver state machine

        AVAILABLE → OFFERED → ON_TRIP → AVAILABLE
                       │
                       └ timeout or decline → AVAILABLE
        
        /* every transition is a compare and set */
        UPDATE drivers SET status = 'OFFERED'
        WHERE id = :d AND status = 'AVAILABLE';

        Deep dive: where locations live

        OptionVerdict
        A database row per driver, updated every 4 s250,000 writes per second of data that is stale in seconds. The wrong tool.
        Redis geo set per city (GEOADD, GEOSEARCH)In memory and simple; a solid answer for most cities.
        In memory grid of cells (geohash, or H3 hexagons) sharded by cityWhat large platforms build. H3 was created at Uber for exactly this.

        Location history still matters for fares, safety and analytics, so updates are also written to a stream such as Kafka and stored in bulk, never row by row.

        Deep dive: matching

        Matching asks the index for available drivers in the rider’s cell and its neighbors, then ranks them by estimated time of arrival from a routing engine on the road graph, not by straight-line distance: the closest car by air may be across a river. The best candidate gets an offer with a timeout of 10 to 15 seconds; on timeout or decline, the next candidate gets it. The compare and set on driver status guarantees no driver is offered two rides at once.

        Deep dive: live tracking

        During a trip, the driver app keeps sending locations; the location service publishes them on a channel for that trip, and the rider’s WebSocket receives them. Updates every few seconds are enough, because the client animates smoothly between points.

        What the interviewer probes next

        The driver’s phone loses signal mid-trip.

        The app buffers GPS points and uploads them on reconnect; the fare is computed from the full path afterwards. The rider sees the last known position with a reconnecting notice.

        How does surge pricing work?

        A stream job counts ride requests and available drivers per cell per minute. When demand far exceeds supply, a multiplier applies, smoothed over time so prices do not flicker.

        The matching service crashes in the middle of an offer.

        The ride request and its state are persisted, so another instance resumes it. Offers carry timeouts, and the driver compare and set prevents a double assignment during the handover.

        How is the system partitioned?

        By city or region: location index, matching and trip services all run per region, so a busy Saturday in one city cannot slow another, and an outage stays local.

        V.10Advanced

        Video streaming

        Uploads are heavy but rare; views are light but constant and enormous. Most decisions trade compute at upload time for bandwidth at view time, and bandwidth is the bill that matters.

        Interview prompt

        Design YouTube: creators upload videos, and hundreds of millions of people watch them on any device and any connection.

        Functional requirements

        • Upload large files reliably.
        • Transcode into formats for every device and network.
        • Stream with fast start and few pauses.
        • Metadata, thumbnails, view counts.

        Non-functional requirements

        • Playback starts quickly and rarely rebuffers.
        • Highly available, and originals are never lost.
        • Cost efficient: egress dominates the bill.

        Estimates (assumptions)

        Uploads  1M videos per day × 300 MB          = 300 TB per day of originals
        Views    1B views per day × 100 MB            = 100 PB per day
        Egress   100 PB ÷ 86,400 s                    ≈ 1.2 TB/s, about 9 Tbps
        Verdict  bandwidth dwarfs everything, so the CDN is the core of the design

        API

        POST /v1/uploads                    → { uploadId, chunkUrls[] }
        PUT  {chunkUrl}                     8 MB chunk, resumable
        POST /v1/uploads/{id}/complete
        GET  /v1/videos/{id}/master.m3u8    → list of renditions

        Deep dive: the transcoding pipeline

        Encoding fans out. Every segment and every rendition is an independent task, so a two hour film finishes in minutes on many machines. Thumbnails, captions and content checks run as side branches of the same graph.
        RenditionTypical bitrate, H.264Used for
        240p≈ 0.4 MbpsWeak mobile networks
        480p≈ 1.2 MbpsPhones on cellular
        720p≈ 2.5 MbpsLaptops, good mobile
        1080p≈ 5 MbpsDesktop, TV
        2160p (4K)≈ 15 MbpsLarge TVs on fast broadband

        Bitrates vary a lot with content and encoder; treat these as orders of magnitude.

        Deep dive: adaptive bitrate streaming

        Each rendition is cut into segments of a few seconds and listed in a manifest (HLS or DASH). The player downloads segments one at a time, measures throughput and how much video is buffered, and picks the quality of the next segment accordingly. That is why video drops to a blurrier picture instead of freezing when the train enters a tunnel.

        Deep dive: spending to save

        Newer codecs such as VP9 and AV1 need noticeably less bandwidth for the same quality but cost far more to encode. Spend that compute on popular videos, where every saved byte is multiplied by millions of views; keep the long tail on cheaper encodes. Push popular videos to edge caches ahead of demand; Netflix goes further and places its own cache servers inside internet providers’ networks.

        What the interviewer probes next

        How do you count billions of views?

        Players send events to a stream; a job aggregates them per video per minute and updates counters in batches. Show an approximate count live and reconcile the exact one later, deduplicating by session.

        What changes for live streaming?

        There is no time for a big transcoding graph. Ingest over RTMP or SRT, transcode a few renditions in real time, and use segments of one or two seconds (low latency HLS). The CDN still does the fan-out.

        How do you resume where the viewer stopped?

        The player reports its position every few seconds to a per user key value store, read on the next play.

        How do you detect copyrighted uploads?

        Fingerprint the audio and video during processing and compare against a reference database, then block, monetize or track according to the owner’s policy.

        V.11Advanced

        File sync

        Users expect every file on every device, always, and never a lost edit. Splitting files into blocks named by their content makes uploads small, storage deduplicated, and sync a matter of comparing lists of hashes.

        Interview prompt

        Design Dropbox or Google Drive: files placed in a folder appear on all of a user’s devices and can be shared with others.

        Functional requirements

        • Upload and download files of any size.
        • Automatic sync across devices.
        • Version history and restore.
        • Share files and folders.

        Non-functional requirements

        • Durability above all: never lose a file.
        • Sync quickly while sending as few bytes as possible.
        • Metadata is strongly consistent.
        • Hundreds of petabytes.

        Estimates

        Commits   500M file changes per day          ≈ 5,800 commits/s
        Storage   100M users × 5 GB                  = 500 PB before deduplication
        Upload    a typical edit changes one 4 MB block, not the whole file

        API

        POST /v1/blocks/check { hashes: [...] }
        → which hashes the server is missing
        PUT  /v1/blocks/{hash}       one 4 MB block
        POST /v1/files/commit
        { path, baseVersion, blockHashes: [...] }
        GET  /v1/changes?cursor=c_9812   long poll

        Data model

        files     file_id, namespace_id, path,
                  latest_version
        versions  file_id, version, block_hashes[],
                  size, device_id, created_at
        blocks    hash PK, storage_key, ref_count
        journal   namespace_id, seq, file_id, version

        Deep dive: blocks and deduplication

        Split each file into blocks of about 4 MB and name every block by its SHA-256 hash. Before uploading, the client asks which hashes the server already has and sends only the missing ones. The same block stored by a million users is kept once. Fixed size blocks have one weakness: inserting a byte near the start shifts every boundary. Content defined chunking places boundaries with a rolling hash, so they move with the content and an insert changes only one or two blocks.

        Deep dive: the commit protocol

        1. Upload missing blocks to object storage.
        2. Commit the new version with its block list and the baseVersion it was edited from.
        3. The server accepts only if baseVersion is still the latest, appends to the folder’s journal and bumps latest_version, all in one transaction.
        4. If another device committed first, the commit is rejected and the client saves its edit as a conflicted copy instead of overwriting.

        Metadata lives in a relational database sharded by namespace (a user’s root or a shared folder), so each folder’s journal is on one shard and strictly ordered. Devices remember their position in the journal as a cursor and long poll for anything newer.

        What the interviewer probes next

        A 2 GB upload fails at 80%.

        Blocks already uploaded stay on the server. The client asks again which hashes are missing and resumes from there.

        How do deletes free up space?

        Deletes are soft and kept in version history for a period. A garbage collector removes blocks whose reference count has reached zero.

        A shared folder has 10,000 members.

        The folder is its own namespace with one journal. Members’ devices follow that journal, and change notifications fan out through pub/sub.

        Why not store files in the database?

        Large blobs bloat the database and its backups. Object storage is far cheaper per byte and built for durability; Amazon S3 is designed for eleven nines.

        V.12Advanced

        Collaborative editor

        Several people type into the same paragraph at once, and everyone must end up with exactly the same text. That convergence guarantee is the whole problem; the rest is routing every editor of a document to the same place.

        Interview prompt

        Design Google Docs: several users edit one document at the same time and see each other’s changes and cursors live.

        Functional requirements

        • Real time co-editing of rich text.
        • Collaborators’ cursors and selections.
        • Comments and version history.
        • Offline edits merge on reconnect.

        Non-functional requirements

        • Remote edits appear within about 100 ms in the same region.
        • All copies converge to the same text.
        • No acknowledged keystroke is lost.

        Estimates (assumptions)

        Peak load  1M open documents × 2 editors × 1 op/s ≈ 2M operations/s
        Servers    ≈ 10,000 active documents per server    ≈ 100+ collaboration servers
        Op log     ≈ 50B ops per day × 100 B                ≈ 5 TB per day before compaction

        Data model

        documents   doc_id, title, owner,
                    snapshot_version
        snapshots   doc_id, version, content
        operations  doc_id, version, author,
                    op, created_at
                    append only, version + 1 each

        An operation on the wire

        { "doc": "d_71", "baseVersion": 4182,
          "op": { "insert": "X", "at": 1 },
          "clientOpId": "c9-0012" }
        → { "ack": "c9-0012", "version": 4183 }

        Deep dive: OT or CRDT

        Operational transformation (OT)CRDT
        How it convergesA server orders operations and transforms each against concurrent ones it already appliedEvery character gets a unique ID; merges are commutative, so any order gives the same result
        ServerRequired, one authority per documentOptional; peers can sync directly
        Offline editingPossible, but long divergence is hardNatural fit
        OverheadSmall operationsMore metadata: IDs per character, deletion markers
        ExamplesGoogle DocsYjs, Automerge, many local-first apps

        Deep dive: one document, one server

        Transformation needs a single ordering point, so every editor of a document must reach the same collaboration server. Route by doc_id with consistent hashing and a lookup service. That server holds the document in memory, assigns each operation the next version, appends it to the log, and broadcasts it. A client sends each operation with the version it was based on; if the server has moved on, it transforms the operation before applying it.

        Cursors and selections are presence data: broadcast a few times per second, never persisted.

        What the interviewer probes next

        The server for a document crashes.

        Another server claims the doc_id, loads the latest snapshot plus the operations after it, and clients reconnect. Each client resends unacknowledged operations with their base version and client operation ID, and the server transforms them and drops any it already applied.

        How does version history work?

        Replay operations from the nearest snapshot to reach any version. For the history panel, group operations into sessions by author and time.

        Why not just lock the paragraph being edited?

        It is simpler but feels broken: people expect to type anywhere. OT and CRDTs give lock-free editing.

        What about very large documents?

        Split them into blocks or pages, load lazily, and scope operations to blocks so a change in chapter 9 never touches chapter 1.

        V.13Advanced

        Distributed key value store

        The advanced classic, modeled on Amazon’s Dynamo paper. Every technique from Part IV shows up here, combined, and the interviewer will ask what each one costs.

        Interview prompt

        Design a key value store like Dynamo or Cassandra: put(key, value) and get(key) across hundreds of nodes, always writable.

        Functional requirements

        • put and get for values under 10 KB.
        • Tunable consistency per request.

        Non-functional requirements

        • Petabytes of data, horizontal scaling, no downtime to add nodes.
        • High availability: writes succeed even during failures.
        • Low latency at the 99th percentile.
        ProblemTechniqueWhat it gives you
        Data larger than one machineConsistent hashing with virtual nodesEven spread; adding a node moves only its share
        Node lossReplicate to the next N distinct nodes clockwiseCopies on separate machines, racks and zones
        Freshness against latencyQuorums N, R, WChoose per request
        Concurrent writes to one keyVector clocks or last write winsDetect conflicts, then resolve them
        Temporary failureSloppy quorum and hinted handoffWrites succeed while a replica is down
        Permanent failure, driftAnti-entropy with Merkle treesReplicas resync, moving only what differs
        Knowing who is aliveGossip protocolNo central monitor; news spreads in O(log N) rounds
        Fast writesLSM tree: commit log, memtable, SSTablesSequential disk writes only

        Partitioning and replication use exactly the ring from IV.3: pick a key there to see its three replicas.

        Simulator: quorum reads and writes

        Presets
        Consistency
        Writes survive
        Reads survive

        Deep dive: conflicts and vector clocks

        With W below N, two clients can update the same key on different replicas at once. A vector clock is a list of [server, counter] pairs attached to each version. If one version’s counters are all greater or equal, it descends from the other and wins. If neither dominates, the writes were concurrent and the system keeps both for the client to merge.

        D3 and D4 conflict. Each has a counter the other lacks (Sy versus Sz), so neither descends from the other. The next reader receives both, merges them (for a shopping cart, the union of items) and writes D5, which dominates both. Last write wins would have silently dropped one.

        Deep dive: failures

        Detecting them

        Every node keeps a membership list with heartbeat counters and gossips it to a few random peers each second. If a node’s counter stops rising for a while, peers mark it down. News reaches every node in about log N rounds, with no single monitor to fail.

        Surviving them

        Hinted handoff: if a replica is down, the write goes to the next healthy node with a note naming the real owner, and is handed back when it returns. Merkle trees let two replicas compare hashes of key ranges and sync only the branches that differ.

        Why writes are fast. A write is one sequential append to the commit log plus an in memory insert; the memtable is flushed to an immutable sorted file when full. Reads check the memtable first, then use bloom filters to skip SSTables that cannot contain the key. Background compaction merges SSTables.

        What the interviewer probes next

        Why not just use last write wins?

        It is simple but silently discards one of two concurrent writes, and clock skew between servers makes the choice arbitrary. Fine for caches and full overwrites; wrong for carts or counters where both updates should survive.

        How does a read find the latest version?

        The coordinator reads R replicas and returns the newest version, or all concurrent ones. Replicas that returned stale data are fixed in the background (read repair).

        How do you add a node with no downtime?

        The new node takes its virtual positions on the ring, streams the key ranges it now owns from their current owners, then starts serving. Only neighboring ranges move.

        How do you place replicas across data centers?

        Walk the ring but skip nodes in a rack or zone that already holds a copy, so N replicas land in N failure domains. Cassandra calls this a network topology strategy.

        V.14Advanced

        Payment system

        Low throughput, zero tolerance. Here the hard part is not scale but correctness across unreliable networks: never charge twice, never lose money, and prove it with a ledger.

        Interview prompt

        Design the payment backend for a marketplace: customers pay, sellers get paid out, and the books always balance.

        Functional requirements

        • Pay-in: charge the customer through a payment service provider (PSP) such as Stripe or Adyen.
        • Pay-out to sellers, refunds.
        • Reconciliation with the PSP.

        Non-functional requirements

        • Correctness first: no double charges, no lost payments.
        • Auditable history of every movement of money.
        • Card data never stored by us (PCI DSS scope).

        Estimates

        Payments  1M per day ÷ 86,400 s     ≈ 12 per second
        Peak      Black Friday, 10×         ≈ 120 per second
        Verdict   one database handles this; spend the design effort on correctness
        Money moves in the PSP; we move records. The payment service records the order before calling out, the PSP confirms by webhook, and only then do the wallet and ledger change. Reconciliation compares the PSP’s settlement file with the ledger every night.

        The flow

        1. Checkout sends a payment request with an idempotency key, for example the order ID. The payment service stores the order as NOT_STARTED.
        2. The executor calls the PSP. Card details never touch our servers: the PSP’s hosted page or SDK turns them into a token.
        3. The PSP answers, or calls our webhook later, and the order moves to SUCCESS or FAILED.
        4. On success, the wallet balance and ledger entries are written in one transaction, or through the outbox pattern if they live in different services.
        5. Nightly, reconciliation matches the PSP settlement file line by line against the ledger and flags every mismatch.

        Simulator: a timeout, a retry, and the idempotency key

          Charges at the PSP

          Charges
          0
          Buyer charged
          €0

          Idempotency table

          KeyStored result
          Empty

          A timeout does not mean the charge failed. The server may have finished the work and only the response was lost. Retrying is necessary (at least once), and the idempotency key makes the retry harmless (at most once). Together they give the exactly once effect.

          Deep dive: the double entry ledger

          EntryAccountDebitCredit
          1Buyer, card via PSP€50.00
          2Seller payable€45.00
          3Platform fee revenue€5.00
          Total€50.00€50.00

          Every transaction debits and credits the same total, so the sum of all entries is always zero. Entries are append only: a refund is a new pair of entries, never an edit. If the books ever fail to balance, you can trace exactly which transaction broke them.

          Deep dive: retries and distributed steps

          • Retry with exponential backoff and jitter: about 1, 2, 4, 8 seconds, each randomized so thousands of clients do not retry in lockstep. Then stop and alert.
          • Check before retrying an unknown outcome: query the PSP for the payment’s status using the same key.
          • Outbox pattern: write the state change and the event to publish in the same database transaction; a relay publishes the event afterwards. No “saved but never announced” gaps.
          • Saga for multi-step flows: each step has a compensating action (a refund undoes a charge) instead of a distributed lock.
          • Store money as integers in minor units (cents) with a currency code. Floating point cannot represent 0.10 exactly.

          What the interviewer probes next

          The PSP is down. What do customers see?

          Route to a secondary PSP where contracts allow, protected by a circuit breaker so failing calls stop quickly. If none is available, fail fast with a clear error; never leave a payment in an unknown state without a follow up job to resolve it.

          How are sellers paid out?

          A daily batch sends pay-outs through the PSP or a bank, with the same idempotency keys and ledger entries. Funds are held for a period so refunds and chargebacks can be covered.

          What does reconciliation do with a mismatch?

          Classify it. Known patterns (a delayed webhook) are fixed automatically; unknown ones go to a finance queue for manual review. The point is that no discrepancy goes unnoticed for more than a day.

          What is a chargeback, and how does the ledger handle it?

          The customer disputes the charge with their bank, which pulls the money back. The ledger records reversing entries against the seller’s balance, which may go negative until the next pay-out settles it.

          Part VI

          Review

          The questions that come up in almost every interview, the tradeoffs behind them, and a quiz to check what stuck.

          VI.1Mixed

          Most asked questions

          The conceptual questions that come up again and again, with answers short enough to say out loud. Starred ones come up most often. Use the flashcards to practice answering before you peek.

          VI.2Mixed

          Tradeoff sheet

          Interviews reward the sentence “I chose A over B because of this requirement, and it costs us this.” Here are the pairs you will use most.

          ChoicePick the first whenPick the second when
          Scale up or scale outTraffic is modest and simplicity winsYou need failover or you have hit hardware limits
          SQL or NoSQLRelations, joins, transactions, evolving queriesSimple access by key at huge scale, flexible schema
          Strong or eventual consistencyMoney, inventory, uniquenessFeeds, counters, carts, anything that tolerates seconds of lag
          Push or pull fan-outMost authors have modest audiencesFew authors with enormous audiences
          Synchronous call or queueThe caller needs the result nowWork is slow, bursty or can finish later
          Cache-aside or write-throughRead heavy; misses are acceptableReads must see fresh data right after writes
          Normalize or denormalizeWrites dominate and data must not driftReads dominate and joins are too slow
          Hash or range shardingEven spread, point lookupsRange scans such as “orders from last week”
          301 or 302 redirectYou want minimal loadYou need analytics or changeable links
          Long polling or WebSocketOccasional updates, simple infrastructureReal time, two way, high frequency
          VI.3Mixed

          Self check quiz

          Fifteen questions that come straight from the case studies. Each answer explains why, so a wrong pick is still useful.

          0 of 15 answered