#Fundamentals and estimation questions
AddedThis page is for the last few days before an interview, when you no longer need to learn a topic but need to say it well, quickly. Every question here is one an interviewer might ask, or one you should be able to answer for yourself in the middle of a design. The first paragraph of each answer is the version you'd say out loud: 30 to 90 seconds, with the decision first and the reason straight after. The walkthrough under it is the reasoning you need when the follow-up is "why?", and the link at the end goes to the topic that teaches it properly.
Think of it as a deck of flashcards with the working shown. Read the question, close your eyes, answer it in your own words, then open the answer and compare: did you name the decision, the number that drives it, and the trade-off? If one of the three is missing, that's the card to go back to. The estimate questions have small runnable models; their output was produced on Node 26 and is re-checked every time the site is validated, so you can trust the arithmetic and concentrate on the reasoning.
This first group covers the interview itself, estimation, latency and availability numbers, and the scaling fundamentals from the first two chapters.
Show answer
About 5 minutes of introductions, 35 of design, 5 for my questions, and inside the 35 a fixed order: requirements (5), estimates (2), API (3), data model (2), high-level design (10), deep dives (10), wrap-up (3). "I aim to have a complete, working high-level design on the board by about minute 27, so the second half can go on the two hardest parts. I write the requirements and numbers down where we can both see them, and I check in with you at each phase boundary."
- The order matters more than the exact minutes: requirements decide the numbers, the numbers decide the components, the API and data model decide what the boxes have to do. Skipping a step means guessing later.
- Requirements get 5 minutes, not 15. Ask only the questions whose answers change the design (scale, read/write ratio, latency, consistency, durability), write the answers down and move on.
- The high-level design must cover every functional requirement end to end before you go deep. A beautiful deep dive on a design that can't do one of the core features scores worse than a complete simple design.
- Deep dives are where senior candidates earn their level: pick the two hardest parts (or the ones the interviewer steers you to), compare at least two alternatives and say why one wins at these numbers.
- The wrap-up names bottlenecks, failure modes and what you'd do next. It shows you know the design's limits, which is a signal in itself.
Learn more: A repeatable framework and What the interviewer is looking for.
Show answer
The questions whose answers change the design. "Which features are in scope: upload, feed, follow, likes, comments? How many daily users, and how often does each upload and view? So what's the read/write ratio? Which paths must be fast, and how fast? How bad is downtime, and is it worse for viewing than uploading? Can a user see a slightly stale feed? Can we ever lose a photo?" Then I write down three to five core features, the numbers, and what's out of scope.
- Features decide the API and the data model; agreeing that comments and search are out of scope saves ten minutes of design you'd never be scored on.
- Users and actions per user feed the estimate. "50 M daily users, 0.1 uploads and 100 views each" turns into QPS, storage and bandwidth in two minutes.
- Read/write ratio decides the main problem: 1,000 views per upload means caching and a CDN matter far more than write scaling.
- Latency, availability, consistency and durability are the non-functional requirements, and each needs a number or a guarantee: "feed p99 under 300 ms", "viewing 99.99%, uploads 99.9%", "a feed may be 30 s stale", "no photo is ever lost".
- Questions that don't change the design (the colour of the button, which cloud) waste time. If you can't say what you'd do differently for each answer, don't ask it.
Learn more: Functional and non-functional requirements.
Show answer
Because no design can fail it, so it can't be checked. A requirement needs a number and a scope: "50 M daily users at a 3× evening peak; viewing 99.99% available and uploads 99.9%; feed p99 under 300 ms; a deleted photo disappears everywhere within a minute." Now every box I draw can be tested against it, and the interviewer can see that I know which parts deserve the expensive guarantees.
- "Scalable" becomes a load: daily users, actions per user, peak factor, growth over the next years.
- "Highly available" becomes a target per path, because the cost of each nine is high: 99.99% allows about 4.3 minutes of downtime a month, 99.9% about 43 minutes.
- "Fast" becomes a percentile and a path: p99 (not the average) on the feed read, measured at the edge.
- "Consistent" becomes a staleness bound or a guarantee such as read-your-writes, and "durable" becomes "never lose an acknowledged upload".
- Saying the adjectives scores nothing because interviewers hear them in every round; replacing them with numbers is one of the cheapest ways to look senior.
Learn more: Functional and non-functional requirements and Common mistakes.
State the peak factor you assume, and check the "100,000 seconds a day" shortcut.
Show answer
About 116 K reads a second on average and 347 K at a 3× peak, and about 14 K writes a second at peak: 25 reads per write. "So reads dominate: stateless API servers behind a load balancer, a cache in front of the database for the hot reads, and the write path is a few primary databases' worth, not a write-scaling problem."
// A social app: 200M daily users, 50 reads and 2 writes each per day, peak = 3x average.
const dau = 200e6, readsPerUser = 50, writesPerUser = 2, peakFactor = 3;
const DAY = 86_400;
const k = (n) => `${(n / 1000).toFixed(1)}K`;
const readQps = (dau * readsPerUser) / DAY;
const writeQps = (dau * writesPerUser) / DAY;
console.log(`reads: ${k(readQps)}/s average, ${k(readQps * peakFactor)}/s peak`);
console.log(`writes: ${k(writeQps)}/s average, ${k(writeQps * peakFactor)}/s peak`);
console.log(`read:write = ${readsPerUser / writesPerUser}:1`);
// The whiteboard shortcut: a day is "about 100,000 seconds".
const shortcut = (dau * readsPerUser) / 1e5;
console.log(`shortcut reads: ${k(shortcut)}/s (${((shortcut / readQps - 1) * 100).toFixed(1)}% vs exact)`);reads: 115.7K/s average, 347.2K/s peak
writes: 4.6K/s average, 13.9K/s peak
read:write = 25:1
shortcut reads: 100.0K/s (-13.6% vs exact)- Requests per day are users × actions:
200 M × 50 = 10 billionreads and200 M × 2 = 400 Mwrites. - Divide by the 86,400 seconds in a day:
115.7 Kreads and4.6 Kwrites per second on average. - Multiply by the peak factor, here 3 (a rule of thumb for consumer apps; ask or state it):
347 Kreads and13.9 Kwrites per second. Systems are sized for the peak, not the average. - The shortcut "a day is about 100,000 seconds" gives
100 Kreads a second, 13.6% low. That's fine for an order of magnitude, but say you're rounding, and round in the direction that makes the system bigger if it matters. - Read the result back as decisions: 347 K reads a second means a cache tier and many app servers; 14 K writes a second means one carefully chosen partition key, and probably sharding within a few years.
Learn more: Back-of-the-envelope estimation.
200 M daily users each send 40 messages a day; a stored message is about 200 bytes with its ids and timestamps; three replicas.
Show answer
About 1.6 TB of new messages a day, 0.58 PB a year, and about 8.8 PB over five years with three replicas. "That rules out one database server: it's a partitioned store, keyed by conversation so each chat is read from one partition, with older history possibly moved to cheaper storage."
// Chat storage: 200M daily users each send 40 messages a day, 200 bytes per message
// (text plus ids and timestamps), 3 replicas, kept 5 years. Decimal units.
const dau = 200e6, msgsPerUser = 40, bytesPerMsg = 200, replicas = 3, years = 5;
const perDay = dau * msgsPerUser * bytesPerMsg; // bytes of new messages per day, one copy
const TB = 1e12, PB = 1e15;
console.log(`messages per day: ${(dau * msgsPerUser / 1e9).toFixed(0)} billion`);
console.log(`one copy: ${(perDay / TB).toFixed(1)} TB/day, ${(perDay * 365 / PB).toFixed(2)} PB/year`);
console.log(`${years} years x ${replicas} replicas: ${(perDay * 365 * years * replicas / PB).toFixed(1)} PB`);
console.log(`write rate: ${Math.round(dau * msgsPerUser / 86_400).toLocaleString("en-US")} messages/s average`);messages per day: 8 billion
one copy: 1.6 TB/day, 0.58 PB/year
5 years x 3 replicas: 8.8 PB
write rate: 92,593 messages/s average- Messages per day:
200 M × 40 = 8 billion, which is about 92,600 writes a second on average before any peak factor. That number alone says "partitioned writes". - Bytes per day for one copy:
8 billion × 200 B = 1.6 TB. Times 365 is0.58 PBa year. - Five years and three replicas multiply:
0.58 × 5 × 3 ≈ 8.8 PB. Forgetting copies (replicas, backups, indexes) is one of the classic estimation slips. - Media is not in this number. Photos and voice notes would dwarf text and belong in object storage with only their keys in the message row.
- Decimal units (1 TB = 10^12 bytes) are deliberate: estimates use powers of ten, and the binary/decimal gap (about 10% at TB) doesn't change any decision.
Learn more: Back-of-the-envelope estimation and Partitioning and sharding strategies.
Show answer
No. 17.5 GB/s is 140 Gbit/s, because network links are sold in bits and storage in bytes. "So even the average doesn't fit one 100 Gbit/s link, and the 3× peak is about 420 Gbit/s. This traffic has to come from a CDN, with object storage as its origin."
- Multiply bytes by 8 to get bits:
17.5 GB/s × 8 = 140 Gbit/s. - Peak is the average times the peak factor:
140 × 3 = 420 Gbit/s. - Mixing the two units understates network needs by a factor of 8, which is the difference between "one link" and "a CDN contract".
- The other slips on the same list: per day vs per second (forgetting to divide by 86,400), DAU vs MAU, forgetting copies, and sizing for the average instead of the peak.
- This is chapter 1's photo app: 57.9 K image views a second at 300 KB each. The number justifies the CDN box on its own.
Learn more: Back-of-the-envelope estimation and CDNs.
Show answer
DRAM about 100 ns, NVMe 4 KB read about 60 µs, same-zone round trip about 300 µs, zone to zone about 1–1.5 ms, US east to the UK about 78 ms. "The surprise is the middle: on modern hardware a local SSD read is faster than asking another server in the same data centre. And crossing an ocean costs about 250 times more than a round trip inside the zone (78 ms against 300 µs), which is why a request's synchronous path stays inside one region."
- Memory is effectively free for request handling: about 100 ns for a cache miss to DRAM, a thousand times less than any network hop.
- NVMe reads are tens of microseconds (60 µs is the typical queue-depth-1 figure for a data-centre SSD). "Disk is slow" is a spinning-disk-era rule.
- Same zone is about 300 µs per round trip, so an in-process cache in front of Redis is worth it for the very hottest keys.
- Zone to zone is about 1–1.5 ms. Synchronous replication across zones is affordable for a database commit.
- Cross-region is tens to hundreds of milliseconds (78 ms US east to UK, 261 ms UK to Australia, Azure's 2026 medians), and physics sets the floor: light in fibre covers about 1 km every 4.9 µs. One such call can eat most of a latency budget.
Learn more: Latency numbers to know.
Show answer
Three round trips at about 78 ms each: one for TCP's handshake, one for TLS 1.3's, and one for the request itself. "So I keep connections open and pooled between services, terminate TLS at an edge close to users, and never put a fresh cross-region connection on a request's critical path."
- TCP needs SYN, SYN-ACK before data can flow: one round trip, 78 ms.
- TLS 1.3 needs one more round trip to agree keys (TLS 1.2 needed two): another 78 ms.
- Only then does the HTTP request go out and the response come back: the third 78 ms, plus the server's own time.
3 × 78 = 234 ms, against a typical 100–300 ms budget for the whole request. A warm pooled connection pays only the last round trip. (This leaves out the DNS lookup, often cached; a cold one adds another round trip to the resolver.)- CDNs and edge proxies help users for the same reason: the handshakes happen with a nearby edge (a few milliseconds away), and the edge keeps warm connections to the origin.
Learn more: Latency numbers to know and CDNs.
A load balancer (99.99%), three API instances (99.9% each, any one can serve), a database (99.95%), and optionally a payments API (99.9%) called synchronously.
Show answer
99.9% allows about 43 minutes a month and 99.99% about 4.3 minutes. Redundant copies multiply their failure chances (parallel), while dependencies on the request path multiply their availabilities (serial). "Three API instances are effectively never all down, so the path is set by the single load balancer and database: about 99.94%. Adding one synchronous 99.9% dependency drops it to 99.84%, more than an hour a month, so non-critical dependencies get a fallback or move off the request path."
// A request path: load balancer -> API -> database, plus a call to a payments API.
// Availabilities per month; a 30-day month has 43,200 minutes.
const MONTH_MIN = 30 * 24 * 60;
const serial = (...parts) => parts.reduce((a, b) => a * b, 1);
const parallel = (a, n) => 1 - (1 - a) ** n; // n independent copies, any one is enough
const show = (label, a) => {
const down = (1 - a) * MONTH_MIN;
const text = down < 1 ? `${(down * 60).toFixed(3)} s` : `${down.toFixed(1)} min`;
console.log(`${label.padEnd(36)}${(a * 100).toFixed(7).padStart(12)}% ${text.padStart(9)} down/month`);
};
const lb = 0.9999, api = 0.999, db = 0.9995, payments = 0.999;
show("one API instance", api);
show("API x3 behind the LB (parallel)", parallel(api, 3));
show("LB + API x3 + DB (serial)", serial(lb, parallel(api, 3), db));
show("+ payments called synchronously", serial(lb, parallel(api, 3), db, payments));one API instance 99.9000000% 43.2 min down/month
API x3 behind the LB (parallel) 99.9999999% 0.003 s down/month
LB + API x3 + DB (serial) 99.9400049% 25.9 min down/month
+ payments called synchronously 99.8400649% 69.1 min down/month- Downtime is
(1 − availability) × period. With a 30-day month of 43,200 minutes, 99.9% gives0.001 × 43,200 = 43.2minutes; each extra nine divides it by ten. - Parallel copies fail only when all fail at once:
1 − (1 − 0.999)³ = 1 − 10⁻⁹, a few milliseconds a month. Redundancy is how you get nines from cheaper parts (assuming failures are independent, which shared deploys and configs often break). - Serial dependencies must all work:
0.9999 × 0.999999999 × 0.9995 ≈ 99.94%, 25.9 minutes a month. The single database dominates. - One more synchronous 99.9% dependency multiplies in:
99.94% × 99.9% ≈ 99.84%, 69.1 minutes. Your availability can't beat your critical dependencies' unless you can do something useful without them: serve cached data, queue the work, degrade the feature. - Set the target per path: viewing might deserve 99.99% (automatic failover, no single points of failure across zones), uploads 99.9%.
Learn more: Availability, SLAs, SLOs and SLIs and Graceful degradation and fallbacks.
Show answer
The SLI is the measurement, the SLO is the target you set yourself, the SLA is the contract with consequences, and the error budget is the SLO's allowed failure, spent deliberately. "For the feed, the SLI is the fraction of requests that succeed within 300 ms, the SLO is 99.9% of them over 30 days, and any SLA we sign is looser than that. The 0.1% of allowed failures is the error budget: while there's budget left we ship quickly, and when a burn-rate alert says we're spending it fast, we slow releases and fix reliability."
- SLI: a precise definition, including what counts as good. "Non-error response within 300 ms, measured at the load balancer" is an SLI; "uptime" isn't.
- SLO: target plus window, "99.9% over 30 days". Internal SLOs are stricter than external SLAs, so you notice and fix problems before you owe anyone credits (S3 Standard is designed for 99.99% but its SLA promises 99.9%).
- SLA: the legal promise, usually service credits. Credits are a discount, not insurance; they rarely cover an outage's real cost.
- Error budget:
1 − SLO. At 99.9% that's 43 minutes of full outage a month, or a longer partial one. It turns "should we deploy on Friday?" into a number. - Alerts page on burn rate, how fast the budget is being spent: 2% errors against a 99.95% SLO is a burn rate of 40, which would empty a 30-day budget in under a day.
Learn more: Availability, SLAs, SLOs and SLIs and Observability: logs, metrics and traces.
Show answer
Horizontal for the stateless tiers, from the start; vertical first for the database, for as long as it's enough. "App servers hold no state, so I put them behind a load balancer and add instances, spread over zones. The database is the hard part: I'd scale it up, move reads to replicas and caches, and only shard when the write rate or the data size forces it, because sharding costs cross-shard queries and transactions for ever."
- Vertical (a bigger machine) needs no code change and keeps transactions and joins simple, but has a ceiling, a price curve that gets steep near the top, and is still one machine.
- Horizontal (more machines) needs the tier to be stateless or partitioned, and gives you redundancy as a side effect: N + 1 instances across zones survive losing one.
- More app servers don't help a database bottleneck; they add connections and queries to it. Say which tier is the bottleneck before saying how you'll scale it.
- Spreading capacity over more, smaller units also reduces spare capacity: with N zones, surviving one loss needs only
N / (N − 1)of peak, 1.5× with three zones rather than 2× with an active-standby pair. - The usual order for a database: tune and index, scale up, read replicas, cache, then partition.
Learn more: Vertical vs horizontal scaling and Stateless services and where the state goes.
Show answer
Into shared stores built to hold it: sessions in Redis behind an opaque cookie, files in object storage via presigned uploads, durable data in the database, counters and rate limits in Redis. "The test I apply is: what breaks if the next request goes to a different instance, or this instance disappears right now? If the answer is nothing, the load balancer can use any algorithm and autoscaling can add and remove instances freely."
- Sessions: an HTTP-only cookie carries an opaque id; the session lives in a replicated Redis. Services that only need identity get a short-lived signed token from the gateway.
- Uploads: the browser sends bytes straight to object storage with a presigned URL; the API never holds the file between requests.
- Hidden local state is the usual bug: in-memory rate limiters (N instances let a client do N× the limit),
setIntervaljobs that now run on every instance, caches that must be correct. - Sticky sessions are a workaround, not a design: losing a server logs out its users, and with
hash % Nrouting, losing one of three servers moved two-thirds of users in chapter 2's model. - The only stickiness worth keeping is inherent: a WebSocket lives on one server, and even there messages go through pub/sub so any server can deliver them.
Learn more: Stateless services and where the state goes.
Show answer
L7 at the edge for HTTP, L4 where I need raw connection throughput or non-HTTP protocols, and per-request balancing for long-lived HTTP/2 or gRPC connections. "An L7 balancer terminates TLS and sees the request, so it can route by host and path, retry idempotent requests, run canary weights and report per-route errors. An L4 balancer only sees connections, which is cheaper and protocol-agnostic, but it pins a whole HTTP/2 connection, and every request on it, to one backend."
- L4 works on TCP/UDP: it picks a backend per connection by IP and port. Millions of connections, low cost, client IPs preserved, no knowledge of HTTP.
- L7 parses HTTP: path and header routing, TLS termination, retries, header injection (request ids), rate limits, per-route metrics.
- The classic trap: gRPC services behind a Kubernetes Service (L4). Each caller opens one HTTP/2 connection and multiplexes everything over it, so one pod runs hot and new pods sit idle. Fix with an L7 proxy or client-side
round_robin, or a maximum connection age. - Both need health checks (active probes plus passive outlier detection), and the balancer itself must be redundant: managed fleets across zones, or two self-managed instances sharing a virtual IP.
- Common shape: anycast or DNS → L4 at the very front → L7 per region → services.
Learn more: Load balancers: L4 vs L7.
Show answer
Round robin when requests are uniform, least outstanding requests when they vary, and power of two choices when there are many balancers or the load data is stale. "Real APIs mix cheap and expensive calls, so round robin lets cheap requests queue behind expensive ones. Least outstanding requests fixes that, but many balancer nodes all seeing the same 'emptiest' server herd onto it. Picking two backends at random and sending to the less loaded one keeps most of the benefit and is robust to stale data, which is why Envoy's least-request balancer works that way by default."
- Round robin keeps no state and is fine for short, similar requests on identical instances. Weighted round robin handles mixed instance sizes and canaries.
- Least connections / outstanding requests adapts to request cost: in chapter 2's simulation at 80% load, p99 fell from about 670 ms to 185 ms for the same capacity.
- Herding: if 50 balancers each send to the server that looked emptiest a moment ago, that server gets flooded. Fresh servers suffer most, so use slow start to ramp a new backend's share over 30–60 s.
- Power of two choices picks two at random and takes the less loaded one. The busiest of 10 backends is chosen only when both picks land on it: 1% of the time instead of 10%.
- Hashing (IP hash, consistent hashing) is for affinity, such as routing a key to the cache node that holds it, not for spreading load.
Learn more: Load-balancing algorithms.
Show answer
No. Losing one zone puts 105% of the load on the other two, so they fail too. "For N active copies to survive one loss, each must run below (N − 1) / N of capacity at peak: 50% for two, 67% for three, 75% for four. I'd alert on that threshold rather than on 90%, and remember that autoscaling only helps after its warm-up time."
- Total work is
3 zones × 70% = 210%of one zone's capacity. - After one zone fails, two remain:
210% / 2 = 105%each. Over 100% means queues grow without bound, timeouts start, retries add more load, and the second zone tips over. - At 60% the survivors would run at 90%: alive, but with latency climbing steeply as utilisation nears 100%.
- The general rule: N copies, each below
(N − 1) / N. More, smaller zones or cells need less idle headroom, which is the economic case for spreading out. - The same arithmetic applies to active-active regions, database replicas taking reads, and cache nodes: redundancy only exists if the survivors can carry the load.
Learn more: Redundancy, failover and single points of failure and Autoscaling.
Show answer
Minutes, not seconds: health checks need about a minute and a half to agree, then resolvers keep the old answer for up to a TTL, plus a tail of clients that cache longer. "With a 60-second TTL that's two to three minutes for most users. If we need seconds, I'd put an anycast front door in front of the regions, so clients keep one stable IP and the edge switches regions itself. DNS is the coarse steering layer, not the per-request balancer."
- Detection: DNS health checks run from several locations and must agree before an endpoint is marked unhealthy, about 90 s with Route 53's defaults.
- Drain: after the record changes, each resolver keeps its cached answer for whatever is left of its TTL, uniformly up to 60 s for a 60 s TTL.
- The tail: some resolvers clamp low TTLs upwards, and long-lived clients keep open connections (and the old IP) regardless of TTL. Set maximum connection lifetimes in clients.
- Anycast (a global load balancer, Global Accelerator or a CDN) advertises one IP from many edges; the edge picks a healthy region in seconds, and clients never re-resolve.
- Inside a region an ordinary load balancer spreads requests across instances, so failover there is seconds.
Learn more: DNS, GeoDNS and anycast and Multi-region architecture.
Show answer
Because the origin pays for misses, and the miss ratio fell from 5% to 1%: five times less origin traffic. "At 100,000 requests a second that's 5,000 a second reaching the origin versus 1,000. So normalising cache keys, adding an origin shield and using stale-while-revalidate are often worth more than adding origin servers. I'd track the origin request rate, not just the hit ratio."
- Origin load is
traffic × (1 − hit ratio):100,000 × 0.05 = 5,000and100,000 × 0.01 = 1,000. - The same arithmetic in reverse is the scary version: a video CDN dropping from 95% to 90% doubles origin egress (10.4 to 20.8 Tbit/s in chapter 12's numbers).
- Levers: strip tracking parameters and sort the rest in the cache key, put an origin shield between edges and origin so each object is fetched once per region, use long TTLs on content-hashed assets, and serve stale while revalidating.
- What not to cache at a shared edge: personalised or authenticated responses (
Cache-Control: private). Cache rules should be allow-lists of public paths, never "cache everything under/api/". - A hit ratio dominated by a few hot files can look excellent while the long tail still hammers the origin, which is why the origin request rate is the number to watch.
Learn more: CDNs and Where to cache.
Show answer
Because autoscaling the app tier multiplies connections and queries to a database that doesn't scale the same way. "Every new app instance opens its own pool, so 3× the instances can mean 3× the connections against one primary. I'd scale the stateless tier on its own saturation metric, cap the maximum to protect the database, put a connection pooler in front of it, and size the database's reads separately with replicas and caches. Autoscaling also takes minutes, so the first minutes of a spike need load shedding."
- Scaling on latency is the worst signal here: the database is slow, latency rises, autoscaling adds app servers, which add load to the database, which gets slower.
- Connections are a hard limit for many databases; a pooler (PgBouncer, RDS Proxy) multiplexes many app connections onto a few database ones.
- Pick the metric that measures this tier's saturation: CPU for CPU-bound code, in-flight requests per instance for I/O-bound Node services, backlog per worker for queue consumers.
- Warm-up matters: chapter 2's model saw a 300-second warm-up more than double the time spent overloaded compared with 60 seconds (13.5 minutes against 5.5).
- Keep a minimum that survives losing a zone, a scale-down stabilisation window so it doesn't flap, and scheduled scaling ahead of events you cause yourself.
Learn more: Autoscaling and Backpressure and load shedding.
Show answer
Stateless tiers and static content in every region behind a global front door; data decided per dataset. "Read-heavy data replicates to every region and each user's writes go to their home region, which also covers data-residency rules. Contended data with invariants, like inventory, has one owning region per item, or a synchronous cross-region quorum if we must lose nothing, accepting 85 ms or more per write. Counters and likes can be active-active with CRDTs. And every region keeps the headroom, or a degraded mode, to take another region's users."
- Routing: latency-based DNS or anycast sends users to the nearest healthy region; a CDN serves assets everywhere.
- Single home per record avoids conflicts: a user's profile is written in their home region and read from local replicas elsewhere (seconds of staleness).
- Invariants ("never oversell", "unique username") need one owner or consensus at the moment of the write. Synchronous replication West Europe ↔ East US costs about 85 ms per commit, at most about 12 sequential commits a second per connection.
- Merge-friendly data (counters, sets of likes) can be written anywhere and merged; last-write-wins on a plain value silently drops one of two concurrent writes.
- "Active-active" without saying what happens when both regions write the same record is the most common multi-region mistake in interviews.
Learn more: Multi-region architecture and Conflict resolution: last-write-wins and CRDTs.
Show answer
Re-run the estimate at 10×, walk the diagram with the new numbers, and fix the first component that breaks, rather than listing scaling techniques. "Stateless services and the CDN scale by adding capacity, so they're not design changes. The stateful parts are: at 10× the writes the database needs partitioning, the cache cluster grows, and hot keys get worse. I'd say what I'd measure first: per-server QPS from a load test, cache hit ratio and database write latency."
- Multiply the estimates you already have; don't invent new ones. Chapter 1's photo app goes from 17.4 K to 174 K feed QPS at peak and from about 419 Gbit/s to 4.2 Tbit/s of image egress.
- Walk each journey through the boxes with those numbers and ask where it saturates first.
- Treat stateless and stateful parts differently: "add servers" is a cost change for stateless tiers, but a design change (partition key, resharding plan, hot-key handling) for stateful ones.
- Mention second-order effects: more servers means more database connections, bigger fan-out, longer cold-cache recovery.
- This scores because it reuses your estimates, finds the first bottleneck instead of all of them, and shows you know where scaling is really hard.
Learn more: Drawing and narrating the design and Vertical vs horizontal scaling.
Show answer
Say it straight away: what the flaw is, which requirement it breaks, and how you'll fix it, then change the diagram. "For example: 'The like counter is a single row, and a viral post would send thousands of updates a second to it. I'll batch the increments through the event stream and write once a second, or shard the counter and sum on read.' Finding and fixing your own mistake is what a good design review looks like, and it's stronger evidence than a design that pretends to be perfect."
- Interviewers can only score what they hear; a silently patched diagram earns nothing, an explained fix earns a trade-off discussion.
- Tie the flaw to a requirement or a number: "at 5,000 likes a second this row is a lock hot spot" is concrete, "this might not scale" isn't.
- Offer the fix with its cost: batching makes the count a second stale; sharding makes reads sum N rows.
- The related mistakes to avoid: jumping to technology before requirements, no numbers, over-engineering (four microservices and Kafka for two endpoints), ignoring failure, and drawing in silence.
- Buzzwords are claims, not designs: every "eventually consistent" or "fault tolerant" invites "how, exactly?", so have the mechanism ready.
Learn more: Common mistakes and Drawing and narrating the design.