Read-heavy systems
Read-heavy prompts test whether you can find the one number that decides the design before you draw a box. The trap is designing for the read rate: "4,000 reads a second, so shard the database" is the wrong sentence, because in a well-built read-heavy system the database never sees the read rate. It sees the miss rate. So the real questions are what can be cached, how long it stays true, and what happens to the few reads that miss. Candidates who reach for sharding before they have said the words "hit rate" have usually lost the room.
Estimate first, then ask what the database actually sees
Spend the first two minutes on arithmetic, out loud: requests per day, divided by 86,400 seconds, times about 3 for the peak. Then bytes per item times items, for storage. Round to one significant figure. The point is not precision; it is finding which number is big enough to force a decision.
In a read-heavy system that number is almost never the read rate itself. It is the read rate times the miss rate. At 10,000 reads a second, a 99% cache hit rate sends 10,000 × 0.01 = 100 reads a second to the database; a 90% hit rate sends 10,000 × 0.1 = 1,000. Nine percentage points of hit rate is a 10x difference in the database you need. So after the estimate, the next sentence is always about what makes the hit rate high: data that never changes, a small hot set, or a precomputed answer.
Say which inputs are assumptions. The read:write ratio and the size of the hot set are the two you are guessing hardest, and the two to replace first once real traffic exists.
Questions
Answer each one yourself before you open it.
01Design a URL shortener that handles 100 million new links a month.
A short link never changes after it is created, so every read is a cacheable key-value lookup and the database only sees cache misses. The shape: a write path that hands out unique keys from pre-allocated counter ranges, a key-value store for key to long URL, and a cache (plus the CDN, for a 301) in front of it that absorbs almost all redirects.
Interview assumptions: 100:1 reads to writes, 500 bytes a row, 5 years kept. Writes: 100M / 30 days / 86,400 s ≈ 40 writes/s. Reads: 40 × 100 ≈ 4,000 redirects/s, perhaps 12,000 at peak. Storage: 100M × 12 × 5 = 6 billion links × 500 bytes ≈ 3 TB. Keys: 62^7 ≈ 3.5 trillion, so 7 base62 characters give 500x headroom over 6 billion. Random keys would collide: by the birthday bound, about (6 billion)² / (2 × 3.5 trillion) ≈ 5 million collisions over 5 years, which is why a counter handed out in ranges beats a random or hashed key. The hit rate is the decision: at 95%, the store sees 4,000 × 0.05 = 200 reads/s; one month of links (100M × 500 bytes = 50 GB) fits in the RAM of a few cache nodes. Replace the 100:1 ratio first once real traffic exists.
Follow-up Marketing wants a click count per link, and one link just went viral at 50x its normal traffic.
The viral link is the easy half: it is one hot key, already in every cache, so a 50x spike on it barely touches the store. Counting is the real change: a 301 is cached by browsers and never comes back, so switch to a 302 for tracked links and write each click as an event to a log that is aggregated in batches. Never increment a counter row on the redirect path, or the one hot link becomes one hot row.
Learn more The slate next to the stove (caching) · The birthday collision (lab)
02Design the home timeline for a social network with 100 million daily users.
Precompute each user's timeline when a post is written (fan-out on write), so reading the feed is one cache lookup instead of a merge across everyone they follow. The exception is accounts with millions of followers: their posts are pulled and merged at read time instead. So the shape is a post store, a fan-out worker fed by a queue, a per-user timeline cache of post ids, and a read path that merges in the few celebrity accounts.
Interview assumptions: 10 feed opens per user per day, 1 post per 10 users per day, 200 followees on average. Reads: 100M × 10 / 86,400 s ≈ 10,000 feed reads/s. Writes: 10M posts / 86,400 s ≈ 100 posts/s. Pull at read time costs 10,000 × 200 ≈ 2 million lookups/s. Push at write time costs 10M × 200 / 86,400 ≈ 20,000 timeline inserts/s. That 100x gap is the whole argument for push. Timeline cache: 500 post ids × 8 bytes = 4 KB per user × 100M ≈ 400 GB, spread across a cache cluster, and only built for users who have logged in recently. Replace the average follower count first; the distribution's long tail is what breaks push.
Follow-up A celebrity with 50 million followers posts.
Pushed, that one post is 50 million inserts; at the 20,000/s the fan-out tier was sized for, that is 50M / 20,000 ≈ 2,000 seconds, over half an hour of backlog for everyone else. So accounts above a follower threshold are not fanned out: their recent posts sit in a small, very hot cache and are merged into each reader's timeline at read time. The hybrid is the expected answer; say where the threshold comes from (fan-out cost versus merge cost), not just that it exists.
Learn more Twitter (X): the case study · The Ledger Everyone Reads (event-driven and the log)
03Design search typeahead: suggest completions as the user types.
Never rank at query time. Precompute the top 10 completions for every prefix in an offline batch job, and serve each keystroke as a single in-memory lookup of prefix to list. Short prefixes are few and extremely hot, so they are cached at the CDN and in the browser; the rest comes from replicated in-memory servers.
Interview assumptions: 100 million searches a day, about 5 requests per search after debouncing, 10 million distinct queries worth suggesting, 20 characters each. Load: 100M × 5 / 86,400 s ≈ 6,000 requests/s, perhaps 20,000 at peak. Latency: a user types a key every 100 to 200 ms, so a suggestion slower than that arrives after the next keystroke and is useless. Memory: 10M queries × 20 prefixes = 200 million prefixes, at about 100 bytes each (prefix plus 10 ids) ≈ 20 GB, which fits in one server's RAM and is replicated rather than sharded. There are only 62² ≈ 4,000 two-character prefixes, and they carry a large share of traffic, which is what makes the edge hit rate high. Replace the requests-per-search figure first; it depends entirely on the debounce.
Follow-up A breaking news term needs to appear in suggestions within minutes, not after tomorrow's batch.
Keep the daily build as the base and add a small real-time layer: a streaming job counts queries over the last few minutes and keeps a trending top-k per short prefix, which is merged over the base list at serve time. Shorten the edge cache TTL on short prefixes to a minute or so, since those are the ones a trending term lands in. Filter the trending layer for abuse before it serves, because it skips the review the batch gets.
Learn more Trees (BST, balancing, heaps, tries) · The Slate, Properly (caching to real depth)
04Design an image hosting service: users upload photos and share links that anyone can view.
Images never change once uploaded, so give each one a content-addressed URL, cache it forever at a CDN, and let the CDN hit rate decide how big the origin is. The shape: uploads go to object storage, a worker generates a few fixed sizes, metadata lives in a small database, and every view is served by the CDN with the origin behind it for misses only.
Interview assumptions: 1 million uploads a day at 2 MB, 1 billion views a day at 200 KB per served size. Uploads: 1M / 86,400 s ≈ 10 uploads/s. Storage: 1M × 2 MB = 2 TB a day, ≈ 0.7 PB a year of originals. Views: 1B / 86,400 s ≈ 10,000 views/s; bandwidth 1B × 200 KB × 8 bits / 86,400 s ≈ 20 Gbit/s. That egress is the number that forces a CDN. At a 95% hit rate the origin serves 10,000 × 0.05 = 500 requests/s and about 1 Gbit/s; at 99%, about 100/s. Because the URL changes whenever the content does, there is no invalidation problem on the normal path. Replace the views-per-upload ratio first.
Follow-up A user deletes a photo, or you receive a legal takedown. It is cached at every edge with a one-year TTL.
Delete from object storage and the metadata store first, so no new edge can fetch it, then call the CDN's purge API for every size's URL. Purges reach edges in seconds on modern CDNs but not instantly, so for content that must be revocable, serve it through short-lived signed URLs checked at the edge instead of a public forever-URL. Say out loud that immutable-and-cached-forever was a trade: it bought the hit rate and cost you deletion.
Learn more Object Storage and the CDN · Instagram: the case study
05Design a pastebin where each paste can expire after a chosen time.
Treat expiry as a read-time check, not a deletion job: a paste past its expiry is dead the moment it is read, whether or not its bytes are gone yet, and a background sweeper reclaims space later. Because live storage is creation rate times lifetime, expiry also keeps the data small enough for one database plus object storage for the bodies.
Interview assumptions: 1 million pastes a day, 10 KB each, 10 reads per paste, a one-week typical expiry. Writes: 1M / 86,400 s ≈ 10/s. Reads: 10M / 86,400 s ≈ 100/s. Kept forever, that is 10 GB a day, ≈ 4 TB a year and growing. With a one-week lifetime, the live set is 1M × 7 × 10 KB = 70 GB and stays there. The load is small, so the design question is correctness, not scale: cache a paste with a TTL no longer than its remaining life, so the cache can never serve a paste the database would refuse. Replace the typical expiry first; a "never expire" default changes the storage answer entirely.
Follow-up Add "burn after reading": the paste can be read exactly once.
The read has to be an atomic get-and-delete in the store (a conditional delete, or a single command like Redis
GETDEL), so two simultaneous readers cannot both get it. That paste must also bypass every cache and the CDN, because a cached copy is a second read. Expect the interviewer to ask about link-preview bots that fetch URLs pasted in chat; the usual fix is a confirmation click before the paste is revealed.Learn more Keys That Die (Expiry) · The Slate, Properly (caching to real depth)