01. Requirements to Architecture — from fuzzy ask to first sketch¶
~14 min read. You cannot draw a solid city if the ask is still foggy.
Built on the ELI5 in 00-eli5.md. The blueprint — the high-level city sketch of zones and links — starts here, from messy requirements.
1) Start by turning the vague ask into verbs and promises¶
In interviews, you hear "design Twitter" and people panic. See. That sentence is not a design.
It is only a business label. Your first job is to unpack it into actions.
Ask: what must users do, what must the system guarantee, and what may be slow, cheap, or eventually consistent?
Simple, no?
Functional requirements are user-visible actions. - Post a tweet. - Follow a user. - Read a home timeline. - Like, reply, search, notify.
Non-functional requirements are operating promises. - Timeline opens under 300 ms. - Data loss must be near zero. - System survives one region failure. - Cost should not explode at 10x traffic.
If you skip this split, your blueprint becomes theatre. Boxes alone look smart, but solve no concrete problem.
A useful opening script is this: - Who are the main users? - What are the top 3 actions? - What is the expected scale today? - What is the expected scale in 2 years? - Which metrics matter most: latency, correctness, cost, or availability? - Which feature can be delayed when load spikes?
So what to do? Write the first answer set before drawing any box.
2) Separate must-have scope from nice-to-have scope¶
Now what is the problem? Most vague prompts hide too much scope.
"Design Twitter" can include DMs, ads, spaces, search, media, and analytics. If you take all of that, you will drown.
Good HLD starts by cutting scope with reasons. Say it clearly.
Phase 1 supports posting, following, and the home timeline. Phase 1 skips DMs, ads ranking, and full-text search.
Why? Because the first blueprint needs a stable center.
The center is content creation, relationship graph, and feed read path. Everything else hangs off that.
Use this table mentally: - Must have: without it, product identity breaks. - Should have: useful, but the core product still works. - Could have: later optimization or monetization layer. - Won't have now: intentionally deferred.
This is not ducking the problem. This is protecting the architecture from fantasy scope.
See another useful split: - Write-heavy features: post, like, follow. - Read-heavy features: home feed, profile view. - Heavy compute features: ranking, recommendations, spam checks. - Offline or batch features: analytics, backfills, email digests.
Once you label these, zones start appearing in the blueprint. Write path often needs durability first. Read path often needs caching and fan-out control. Compute-heavy work may need async handling. Batch work rarely belongs on the hot path.
3) Turn words into numbers before boxes¶
Architecture without math is decoration. Let us do a compact Twitter-style estimate.
Assume 50 million daily active users, 20 app opens per user per day, and one home-timeline fetch per session.
Home timeline reads per day = 50,000,000 × 20. = 1,000,000,000 reads per day. Reads per second average = 1,000,000,000 ÷ 86,400. = 11,574 reads per second.
Real systems do not run at average. Peak can easily be 5× average. Peak read QPS = 11,574 × 5 = 57,870 QPS.
Now posts. Assume each active user creates 2 posts per day. Posts per day = 50,000,000 × 2 = 100,000,000 posts per day. Write QPS average = 100,000,000 ÷ 86,400 = 1,157 writes per second. Peak write QPS at 4× average = 1,157 × 4 = 4,628 writes per second.
Storage next. Assume each post stores: - text and metadata: 1 KB - media pointer and counters: 0.5 KB - indexing overhead: 0.5 KB
Total logical size per post = 2 KB. Daily storage = 100,000,000 × 2 KB = 200,000,000 KB = 200,000 MB = 200 GB per day. Three-year raw storage = 200 GB × 365 × 3 = 219,000 GB = 219 TB. If we keep 3 replicas, physical storage = 219 TB × 3 = 657 TB.
Now see what happened. The prompt stopped being poetry. It became capacity pressure.
That pressure shapes the blueprint. A single database box now looks suspicious. A single feed worker box also looks suspicious.
4) Map numbers to the first box-and-arrow diagram¶
First draw the simplest possible city map.
┌──────────┐ ┌─────────────┐ ┌──────────────┐ │ Client │──→│ API Gateway │──→│ App Services │ └──────────┘ └─────────────┘ └──────┬───────┘ │ ┌───────────────────────┼──────────────────────┐ ▼ ▼ ▼ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ User DB │ │ Post DB │ │ Feed Cache│ └──────────┘ └──────────┘ └──────────┘ │ ▼ ┌────────────┐ │ Feed Worker│ └────────────┘
This is still high level. No class names. No table DDL. No retry library discussion.
Only zones and critical links.
See how requirements map: - Users can post → write API, post service, post store. - Users can follow → relationship service and follow store. - Users see timeline fast → feed service plus cache. - System survives spikes → queues, workers, and replicas. - Abuse must be controlled → auth, rate limits, moderation hooks.
Good first-pass components usually answer these questions: - Where does traffic enter? - Where is source-of-truth data stored? - Which path is read-heavy? - Which path is write-heavy? - Which work should happen later, not inline? - Which boxes will scale independently first?
Let us make the feed path concrete. A user follows 500 accounts. Each followed account posts 4 times per day. Candidate posts per day = 500 × 4 = 2,000 posts.
If the app shows 20 top posts per session, we do not scan all 2,000 every time.
So what to do? We precompute some ranking inputs, cache top slices, and separate feed assembly from raw post writes.
That one thought alone can split your giant box.
Another quick diagram helps: ┌──────────┐ ┌─────────────┐ ┌──────────────┐ │ Writer │──→│ Post Service │──→│ Durable Store │ └──────────┘ └─────────────┘ └──────┬───────┘ │ ▼ ┌────────────┐ │ Feed Queue │ └─────┬──────┘ ▼ ┌────────────┐ │Feed Builder│ └─────┬──────┘ ▼ ┌────────────┐ │ Feed Cache │ └────────────┘
Now the blueprint says something useful. Writes go one way. Feed preparation goes another way. Reads hit a faster path.
That is the whole point of HLD.
Where this lives in the wild¶
- X (formerly Twitter) — tweet creation, fan-out, and timeline serving are split because writes and reads scale very differently.
- Instagram — home feed ranking and caching layers prevent every app open from scanning every followed account on demand.
- LinkedIn — social graph, content creation, and feed assembly are separate zones because connection data and ranking workloads differ sharply.
- YouTube — video upload, metadata storage, recommendations, and watch-page serving live in different system zones with different latency goals.
- Uber — trip intake, driver matching, pricing, and trip storage split early because matching latency and durable trip state conflict.
Pause and recall¶
- Why is "design Twitter" not a requirement list by itself?
- What is the difference between a functional requirement and a non-functional one?
- In the worked example, why did peak read QPS matter more than average QPS?
- Which question usually reveals the first component split: read-heavy vs write-heavy, or language choice?
Interview Q&A¶
Q: Why separate functional and non-functional requirements before drawing the diagram? A: Because the same feature can need very different architectures under different promises. "Post a message" is simple at 100 QPS and very different at 50K QPS. The non-functional targets decide caching, queues, replicas, and storage choices.
Common wrong answer to avoid: "Functional requirements matter for users, non-functional ones are optional polish."
Q: Why estimate capacity before picking services? A: Because capacity exposes pressure points. Without QPS, storage, and peak multipliers, every box looks equally important. With numbers, you can see which path needs caching, partitioning, or async work.
Common wrong answer to avoid: "We estimate later, after the detailed design is ready."
Q: Why start with one simple box-and-arrow diagram instead of detailed components immediately? A: Because the first diagram is a decision surface, not an implementation manual. It should reveal zones, traffic shape, and likely bottlenecks fast. Premature detail hides bad assumptions under pretty labels.
Common wrong answer to avoid: "Senior design means showing more boxes as early as possible."
Q: Why fan-out or feed assembly, not direct database reads for every timeline request? A: Because a hot read path cannot recompute everything from raw data each time. If one user follows 500 accounts, naive reads multiply work badly at peak. Precomputation and caching reduce repeated scanning on the hottest path.
Common wrong answer to avoid: "Databases are fast now, so direct reads are always simpler and fine."
Apply now (5 min)¶
Pick one vague prompt: design WhatsApp status, design hotel search, or design grocery delivery.
Do this on paper: 1. Write 4 functional requirements. 2. Write 4 non-functional requirements. 3. Pick one number for DAU, one for peak QPS, and one for yearly storage. 4. Draw 5-7 boxes only. 5. Mark one read-heavy path and one write-heavy path. 6. Circle the box most likely to break first.
Sketch from memory: Draw the tiny flow from client to gateway to core services to storage. Then redraw it with one async worker added for the hottest path. If you can explain why that worker exists, you understood the file.
Bridge. The first blueprint is still one big city sketch. We now need sharper borders so each zone owns one clear responsibility. → 02-component-decomposition.md