If you remember five things
The whole playbook in one glance. Everything below defends these five moves.
- It is decomposition, not recall. Every "classic" problem is a dozen primitives rearranged. Name the shape and the components pick themselves.
- Find the one hard dimension early. Read-heavy or write-heavy, data scale, freshness, hotspots. One or two of these is the real interview.
- Run the funnel every time. Clarify, estimate, API, data model, high level, deep dive, scale and fail. Same order, no freezing.
- Every number must change a decision. 3,500 peak write QPS says "one primary survives." Numbers that lead nowhere are noise.
- Pick, then defend. Present an option, state its cost, commit. Three options with no choice is a weak no-hire.
1The mental model (what this really is)#
A system design interview is not a trivia test about Kafka or Cassandra. It is a 45-minute simulation of your first week on a team.
- Someone hands you a vague request and watches how you turn it into something buildable.
- A clean diagram with no reasoning behind it reads as memorized, and memorized loses.
- The scoring is on your thinking, not your drawing.
The core idea: almost every classic problem is a recombination of the same dozen primitives.
- Load balancer. Stateless app tier. Cache. SQL database. NoSQL store.
- Object storage for blobs. Message queue. CDN. Search index. Rate limiter.
- That list designs 90 percent of the problems you will ever see, with nothing else.
The problems that look different are usually the same engine rearranged.
- "Design Twitter," "design Instagram," "design a news feed" are one feed engine fit to different read/write ratios.
- Twitter and Instagram are the same feed with a blob store bolted on for photos.
- A URL shortener is a feed with the fan-out removed.
So the skill being tested is decomposition under ambiguity, not memory.
- Take "design a ride-sharing app" and reduce it in five minutes to "write-heavy geospatial matching with a real-time location stream and a payments side-system."
- Once you name the shape of the problem, the components almost pick themselves.
Not whether you reach a "correct" diagram. Whether you can decompose a vague prompt, name the one hard dimension, and defend your calls under pressure. The diagram is the byproduct of good reasoning, never the goal.
2How to recognize it (the signals in a problem)#
Every prompt hides its real difficulty in one or two dimensions. Find them fast, because the first five minutes decide what the interview is actually about.
Read-heavy or write-heavy?
- A URL shortener is read-heavy by a huge margin: created once, clicked a million times, so the ratio runs 100:1 or worse. Optimize reads: cache hard, replicate, precompute.
- A logging or metrics ingestion system is write-heavy: buffer writes, batch them, accept eventual consistency on reads.
How much data, and how fast does it grow?
- This is where estimation lives. 100 million writes a day at 1 KB each is 100 GB a day, 36 TB a year.
- A single modern SQL box comfortably holds a few TB. Past that you are sharding, so 36 TB/year forces the conversation.
- That one number tells you whether one Postgres box survives or you shard from day one.
How fresh must reads be?
- A user can tolerate a 30-second-old value? Then cache and replicate freely.
- They must see their own write immediately (read-your-writes)? That constrains caching and usually routes their reads to the primary for a short window after the write.
Is there a hotspot?
- Celebrity users, a viral tweet, one trending product on sale. Any non-uniform load is a fan-out or hot-key problem, and the interviewer wants you to catch it.
- Uniform load is the easy case nobody is testing you on.
Train the keyword reflexes.
- "Feed," "timeline," "notifications," "followers" should fire fan-out.
- "Nearby," "within 5 km," "closest driver" should fire geospatial index, quadtrees or geohashing.
- "Trending" or "top K" should fire stream processing and approximate counting like count-min sketch.
The first five minutes tell me almost everything. If you name the read:write ratio and the hotspot without being prompted, I relax: this person has run a real system. If you start listing databases before you know the shape of the load, I have quietly started writing the no-hire.
3The template (how it works)#
Use the same opening sequence every single time. It buys you structure and keeps you from freezing. I call it the funnel: start wide, narrow down, go deep on one thing.
1. CLARIFY scope, who uses it, what features are in/out
2. ESTIMATE QPS, storage/year, bandwidth, read:write ratio
3. API define 3-5 endpoints, the contract
4. DATA MODEL entities, access patterns, pick storage per pattern
5. HIGH LEVEL draw the boxes: client -> LB -> service -> store
6. DEEP DIVE pick the ONE hard part, go deep
7. SCALE + FAIL bottlenecks, what breaks, how you recover
Spend your 45 minutes on purpose.
- About 5 minutes on steps 1 and 2.
- About 10 minutes getting a working skeleton up (steps 3 to 5).
- The whole back half on steps 6 and 7, where the senior signal lives.
- Still drawing boxes at minute 30? You spent your budget in the wrong place.
The funnel as a picture:
Here is the estimation shortcut I use at the whiteboard so I never stall:
# back-of-envelope, say the numbers out loud
daily_active_users = 10_000_000
actions_per_user = 10 # e.g. tweets or clicks per day
writes_per_day = daily_active_users * actions_per_user # 100M
seconds_per_day = 86_400 # round to ~100k in your head
write_qps_avg = writes_per_day / seconds_per_day # ~1,160
write_qps_peak = write_qps_avg * 3 # peaks run 2-3x average # ~3,500
bytes_per_write = 1_000 # 1 KB per record
storage_per_day = writes_per_day * bytes_per_write # ~100 GB/day
storage_per_year = storage_per_day * 365 # ~36 TB/yr
Round hard, then make the number earn its place.
- 86,400 becomes 100,000. Nobody wants six digits of precision.
- The ~3,500 peak write QPS is the number that matters: small enough that a single primary with read replicas handles it, so I would not open with a distributed write path.
- Say that conclusion out loud. A number is only useful once it changes a decision.
Estimating 36 TB a year and then putting it all on one unsharded box, with no comment, tells me you computed for show. Every figure you write should flip a switch downstream. If it does not change a decision, do not write it.
4A worked example (design a URL shortener)#
Let me walk the funnel the way I would in a real room, including the back-and-forth I would actually have.
Clarify. Here is the dialogue I would run:
Candidate: So we shorten a long URL to a short code and redirect on lookup. Do we need custom aliases, or is a generated code fine? Interviewer: Generated is fine. Custom aliases are a nice-to-have. Candidate: Do we track click analytics? And do links ever expire? Interviewer: Yes, track clicks. No expiry for now. Candidate: Last one: what scale are we targeting, roughly? Hundreds of millions of links, or billions? Interviewer: Assume about 100 million new links a month.
Why that exchange matters:
- Analytics turns a simple key-value problem into a write-logging problem too.
- The scale number decides whether I shard on day one.
- My working assumptions: yes to analytics, no to expiry, custom aliases out of scope, confirmed as I go.
Estimate.
- 100 million new links a month is about 40 writes/second on average, trivial.
- Clicks are the real traffic. At 100 reads per link on average, reads run ~4,000 QPS, and viral links spike far higher, maybe 10x on a single hot code.
- This is firmly read-heavy, roughly 100:1. That ratio shapes everything: optimize the read path, treat writes as the easy side.
Storage.
- 100M links/month at ~500 bytes each with metadata is 50 GB/month, 600 GB/year. Small.
- A single database holds years of this, so I do not open with sharding, and I say that out loud so the interviewer knows I am not over-engineering.
- If analytics click events balloon that, I push them to a separate append-only store so they do not bloat the link table.
API.
POST /shorten { long_url, custom_alias? } -> { short_url }
GET /{code} -> 301 redirect
GET /{code}/stats -> { clicks, created_at, referrers }
Data model. Two access patterns drive two stores: a fast point lookup by code (the redirect) and an append-heavy stream of click events (analytics). Point lookups want a key-value shape; click events want an append-only log.
| Entity | Key fields | Store | Access pattern |
|---|---|---|---|
| link | code PK, long_url, created_at, owner_id | SQL or KV | point lookup by code |
| click_event | code, ts, ip, referrer, country | append-only log | bulk append, async aggregate |
| counter_block | server_id, block_start, block_end | SQL | atomic ID allocation |
Reveal the answer
The key insight: generating the code.
- The naive answer, hash the URL and take the first 7 characters, has a collision problem and forces a read before every write to check for conflicts.
- The cleaner answer: hand out unique IDs from a counter and base62-encode them. No read before write, no collisions by construction.
ALPHABET = "0-9a-zA-Z" # 62 symbols
# 62^7 = ~3.5 trillion codes, 7 chars is plenty
def encode(n): # integer id -> short string
s = ""
while n > 0:
s = ALPHABET[n % 62] + s
n //= 62
return s
Getting unique IDs at scale without one hot counter.
- Give each app server a block of IDs from a central allocator (say 1,000 at a time).
- The server burns through its block in memory and talks to the allocator once per thousand writes.
- No coordination on the hot path. If a server dies mid-block, we lose a few hundred unused codes, which is fine at 3.5 trillion total.
High level. Client to CDN to load balancer to a stateless redirect service. The service checks a cache (Redis) for the code, falls back to the database on a miss, returns a 301, and writes a click event to a queue for async analytics. The redirect path is almost all cache, which is why read-heavy problems love caching.
Deep dive 1: the cache and the 301/302 trade-off#
With 4,000 read QPS, I want a high hit rate.
- Links follow a power law: a small set of codes gets most clicks, so an LRU cache with a few GB of memory covers the vast majority of traffic.
- Here is the trade-off the interviewer is fishing for, and it is about data, not speed.
301 permanent
- Browser behavior
- Cached by the browser, repeat clicks never reach you.
- Server traffic
- Low: most repeat clicks served client-side.
- Analytics
- Lost on cached repeats, you miss those clicks.
- Pick when
- You do not care about per-click counts.
302 temporary
- Browser behavior
- Not cached, every click comes back to you.
- Server traffic
- Higher: you see every hit.
- Analytics
- Complete: every click is countable.
- Pick when
- The spec says track clicks (it does here).
- I pick 302 because the spec says track clicks, and I say exactly why.
- That tension, performance versus data, is the whole point of the question.
Deep dive 2: thundering herd on a hot key#
The real failure in a read-heavy cache is not a cold start, it is a hot key expiring.
- A viral link spiking at 10x means that when its cache entry expires, thousands of requests miss at the same instant and stampede the database, which can take the primary down.
- Two fixes, and I name the trade-off between them.
Request coalescing.
- The first miss takes a short lock, fetches from the DB, and populates the cache while the rest wait on that single in-flight fetch.
- Simple, but it adds a tiny bit of latency to the waiters.
Early recomputation.
- A key is refreshed shortly before it expires, based on a probabilistic timer, so the entry never actually goes cold under load.
- Trades a little extra cache write traffic for zero stampede risk.
- For a shortener I use coalescing: simpler, and the data rarely changes.
Deep dive 3: ID allocation under failure#
The block allocator removes the write bottleneck, but the allocator itself is now a single point of failure.
- If it goes down, no server can refill its block. Once every server burns through its current 1,000 IDs, writes stall.
- Fix: make blocks large enough to ride out a short allocator outage (a few thousand IDs buys minutes of headroom) and back the allocator with a replicated store so a failover promotes a standby.
The trade-off in block sizing.
- Bigger blocks waste more IDs on crash and make codes less sequential.
- Smaller blocks mean more allocator round trips and less outage tolerance.
- I size the block to the write rate so a server refills roughly once a minute, which balances both.
5Variations and gotchas#
Once you have the shortener, notice how small tweaks spawn whole new classic problems.
Add "millions of followers see your post" and you have the news feed fan-out problem.
- Fan-out on write (push): a copy of each post goes into every follower's feed list. Fast reads, expensive writes, terrible for celebrities.
- Fan-out on read (pull): assemble the feed when the user opens the app. Cheap writes, slow reads.
- The real answer is hybrid: push for normal users, pull for the handful with millions of followers. Catching that celebrity edge case is a strong signal.
Add "show me drivers near me" and you have a geospatial problem.
- A plain latitude/longitude column cannot answer "within 2 km" efficiently: a range query on two independent columns scans far too much.
- You need a geohash or a quadtree so nearby points share a prefix and a range scan finds them.
Add "do not let one user hammer the API" and you have rate limiting.
- Usually a token bucket in Redis keyed by user ID.
The gotchas that bite people.
- Forgetting that caches go stale and not saying how you invalidate them.
- Ignoring the thundering herd when a hot cache key expires and a thousand requests hit the database at once (fix: a short lock or request coalescing, same as deep dive 2).
- Assuming the network is reliable. It is not. Say what happens when the cache is down (serve from the DB at degraded throughput, maybe shed load) or a region goes dark (fail over, accept a short window of stale reads).
6Trade-offs (the ones to say out loud)#
There is no "correct" architecture. There are positions on a few sliding scales, and your job is to state where you land and why.
Consistency versus availability.
- This is CAP in plain terms. During a network partition, do you refuse writes to stay consistent, or accept them and reconcile later?
- A bank ledger picks consistency. A like counter picks availability.
- Name which one your system is and do not hedge.
Latency versus freshness.
- Caching and replication make reads fast but serve older data.
- Decide how stale is acceptable and say the number: "a 60-second-old follower count is fine, a 60-second-old account balance is not."
Cost versus performance.
- Keeping everything in memory is fast and expensive. Say where you would spend and where you would not.
- Over-provisioning for a hypothetical 10x spike is a red flag, not a strength.
Push versus pull.
- The fan-out decision above: write-time work versus read-time work.
- Pick based on your read/write ratio.
Present an option, give its cost, then commit. Three options and no choice is a weak no-hire.
The senior move in one line:
- "I will denormalize the feed into a per-user list. Writes get more expensive, but reads are a single lookup, and this is read-heavy, so that is the right trade."
7Mistakes that cost people the round#
The patterns I see fail strong-on-paper candidates, every loop:
- Jumping to components before scoping. Say "I'll use Kafka and Cassandra" in the first two minutes and you have told me you pattern-matched instead of reasoned.
- Designing for a billion users when the prompt implies a thousand. Over-engineering reads as insecurity. I have failed candidates who sharded a 600 GB dataset that fits on one box.
- Going silent. The round is scored on your reasoning, and I cannot score silence. Narrate: "I'm choosing a queue here because I want writes to survive a downstream outage."
- Hand-waving the hard part. If the whole problem is feed fan-out and you spend twenty minutes on the login API, you missed the point.
- Inventing requirements instead of asking. Unsure whether links expire? Ask. Building the wrong thing is worse than a ten-second question.
- Numbers that do not connect to decisions. Estimate 36 TB a year, then put it on one unsharded box, and I will ask why. Have an answer.
8Your practice path#
Work these in order. Do each one on a whiteboard, out loud, timed to 40 minutes, before you read anyone else's solution.
- URL shortener. Read-heavy design, ID generation, caching, the 301/302 trade-off.
- Rate limiter. Small surface, deep on one data structure (token bucket) and where it lives.
- Pastebin or a key-value store. Storage sizing, TTL, object storage for blobs.
- News feed (Twitter/Instagram). The fan-out problem. The one most companies ask.
- Chat (WhatsApp/Messenger). Long-lived connections, delivery guarantees, online presence.
- Nearby/ride-sharing (Uber, Yelp). Geospatial indexing, real-time matching.
- Web crawler or a notification system. Queues, workers, backpressure, dedup at scale.
Finish all seven and you will recognize the primitives behind almost anything they throw at you.
9Where this shows up#
Every senior and staff loop at the big product companies runs at least one of these rounds, usually two.
- Meta and the social apps lean hard on feed and messaging.
- Amazon folds system design into its bar-raiser round and expects you to connect it to operational reality (on-call, failure modes).
- Google weights the deep dive and your estimation discipline.
- Startups often skip the ceremony and ask you to design the thing they actually build, so expect the domain-specific version.
The bar, stated plainly.
- Mid-level: "can you produce a working design."
- Senior and staff: "can you find the one hard part and defend your trade-offs under pressure."
- Aim for the second.
"You estimated 600 GB a year. Why not shard from the start?"
"Why hand out ID blocks instead of hashing the URL?"
"301 or 302 for the redirect, and why?"
"A viral link's cache entry just expired. What happens?"
"How would you turn this shortener into a news feed?"
What you can now explain in the room
- Why almost every classic problem is the same dozen primitives rearranged for a different read/write ratio.
- How to spot the one hard dimension (read vs write, scale, freshness, hotspot) in the first five minutes.
- The funnel, step by step, and roughly how many minutes each step deserves.
- How to size a system out loud and turn one rounded number into an architectural decision.
- The full URL shortener: code generation, block allocation, the 301/302 trade, and the thundering herd.
- Which trade-off you are making on each sliding scale, and why you committed instead of hedging.
Want to see which of these problems a specific company actually asks and how they score them? Check the company guides to map this pattern onto real interview loops.
Get a prep plan built around your Classic System Design Problems interview
Tell us the role and your interview date. We will send back a dated, day by day plan. Free.