The system design method
Tuesday, 10:05: Maya wants guest reviews on every hotel page in four weeks, and within ten minutes the whiteboard shows Kafka, Cassandra and five new services. StayHub gets about 750 reviews a day.
A simple, repeatable recipe for designing any system, so you build what's needed: not too much, not too little.
- Problem
You're asked to 'design a reviews system' for a hotel platform, in an interview or a real kickoff, and the problem is huge and vague.
- Why the simple system fails
Jumping straight to boxes (Kafka! Cassandra! microservices!) produces a design that solves imagined problems, misses real requirements and can't be defended.
- Engineering concept
Work in a fixed order: requirements → functional/non-functional → estimates → APIs → data model → high-level design → deep dives → trade-offs. Each step constrains the next.
- Technology
Requirement lists, back-of-the-envelope numbers, API sketches (REST), schema sketches (SQL), component diagrams (load balancer, app servers, cache, DB, queue, CDN).
- Trade-offs
Time spent clarifying versus time drawing; a simple design that meets the numbers versus an impressive one that doesn't. Every component must be justified by a requirement.
The problem
2 min- Maya · Product managerGuests ask: is this hotel actually good? I want reviews on every hotel page in four weeks.
- You · Engineer at StayHubTheo already drew it: Kafka, Cassandra, five services. It looks serious.
- Lina · Senior engineer, your mentorIt does. How many reviews a day do you think we'll get?
The big idea
4 minQuestions → numbers → boxes. Every component in a design must point back to a requirement or a number that demands it.
An architect asks how many people live here and how cold the winters get before drawing a floor plan. A family of five isn't a studio flat. Debating Kafka before the numbers is debating an answer before the question.
How it works
5 minEight steps, each feeding the next: clarify scope; split requirements (what it does) from ones (how well: speed, uptime, never losing data, cost); estimate users → → storage, average and peak; sketch the API; the data model; the high-level design; deep-dive the one to three hardest parts; name the trade-offs and what changes at 10×.
- 1Ask
Only guests with a finished stay. Text + up to 5 photos. Hotels can reply. Translation is out of scope.
- 2How well
Reviews block under 200 ms at p99. A submitted review is never lost. Visible within minutes is fine.
- 3Estimate
~750 reviews/day ≈ 0.01 writes/s. ~15 page reads/s at peak. ~270 MB of text a year.
- 4Data
A reviews table in our PostgreSQL, one review per booking, indexed for "hotel X, newest first".
- 5Boxes
A reviews module in the existing app, Redis for summaries, photos on the CDN. Nothing new to run.
- 6Trade-offs
Summaries lag by minutes. Revisit if reviews pass ~50 writes/s.
0.01 writes a second rules out Kafka and Cassandra on its own. The non-functional target, not the features, pointed to the one thing worth care: the hotel page must stay fast.
Watch a decision being made
4 minLina designs StayHub reviews
Reviews (rating, text, photos, hotel replies) on every hotel page within four weeks, on our modular monolith and PostgreSQL, with a plan for 10× growth.
- 1Estimate, and say what each number changes
Reads ~15/s at peak (150 at 10×), writes 0.01/s, text ~270 MB/year, photos ~135 GB/year.
textreads: 120,000 views/day ÷ 86,400 s ≈ 1.4/s → peak ×10 ≈ 15/s writes: 2,500 bookings × 30% = 750/day ≈ 0.01/s photos: 750 × 500 KB × 365 ≈ 135 GB/year → object storage + CDNWhy: An estimate that doesn't change the design is decoration. These rule out new databases and send photo bytes away from the app servers.
- 2Write the API and the table
GET /hotels/{id}/reviews?cursor=,POST /reviewswith an idempotency key.reviews(…, booking_id UNIQUE, rating CHECK 1–10, …), index on(hotel_id, status, created_at DESC).Why:
booking_id UNIQUEmakes "one review per stay" a rule the database enforces; the index matches the hottest query. - 3Deep-dive the hot read path
AVG(rating)over 3,000 reviews per page view costs ~90 ms. Instead, the moderation job updates arating_summaryrow on approval and clears its Redis key: reads take ~2–6 ms.Why: This path runs on every hotel page and has a target. Submission runs every two minutes and needs no cleverness.
- 4Name the trade-off and the trigger
Summaries can lag a few minutes. Revisit if writes pass ~50/s, or reviews need their own deploys.
Why: Every design gives something up. A written trigger means nobody guesses later when it stopped fitting.
In real life
3 minStack Overflow: a few big servers
- Has publicly described serving a huge site from a small number of servers
- Their numbers said a few machines and heavy caching were enough
Fits load that fits a few machines and a team wanting less to run.
Netflix: many services in the cloud
- Has publicly described splitting into many small cloud services
- Hundreds of teams and strict availability needs; lots of tooling
Fits when many teams and strict uptime justify the complexity.
What goes wrong
3 minYou design for assumptions nobody stated.
First minutes on users, scope and non-functional targets.
0.01 writes/s followed by Cassandra shows the estimate was decoration.
State each number's consequence: "so one table".
Ten minutes on submit while the hot read goes unexamined.
Go deep first where it's hardest and riskiest.
Trade-offs
3 min| Approach | Fits when | You pay |
|---|---|---|
| Start simple, evolve | Estimates fit one database; requirements uncertain | Redesign work later if growth is wild |
| Build for projected scale now | Scale is near-certain; a late migration would be very costly | Cost and complexity today, slower delivery |
- Karim · CTOInvestors will ask about 10×. Build reviews as its own service now, so we never migrate.
- Lina · Senior engineer, your mentorAt 10× it's 0.1 writes a second and 150 reads. Still one table. A service is another pipeline and on-call rota for 14 people.
- Karim · CTOAnd when we outgrow it, the migration lands on the worst possible week.
Your job as tech lead
4 minClaude Code can write the code for this. You still own the decisions, and you have to know what good looks like to accept or reject its work.
- The non-functional requirements (under 200 ms p99, never lose a review, visible within minutes) and the input numbers. Never let Claude invent traffic.
- Which boxes the design may add: a module in the existing app by default; a new database or queue needs a number that demands it.
- Where the deep dive goes (the hot read path), the trade-off you accept, and the revisit trigger (~50 writes/s).
Maya wants guest reviews on hotel pages: rating, text, photos, replies, moderation, one per booking. No code yet. Write docs/design/reviews.md: requirements (reviews block under 200 ms p99, never lose a review, visible within minutes), estimates from our numbers only (~120,000 page views/day, ~750 reviews/day, peak 10× average), API, table, design, one deep dive on the hot read path, trade-offs and a revisit trigger. Justify every component with a requirement or number; prefer PostgreSQL, Redis and the CDN.
- Redo one estimate: 120,000 ÷ 86,400 ≈ 1.4 reads/s, ~15 at peak. 500/s means someone else's numbers.
- Every box has a sentence justifying it; "one review per stay" is a
UNIQUEonbooking_id. - The trade-offs section names something given up.
- Kafka, Cassandra or new microservices for ~0.01 writes per second.
- Estimates after the architecture, or ignored by it.
AVG(rating)over thousands of rows on every page view.
Check yourself
2 min- 1.Clarify requirements and scope
- 2.Sketch the high-level design
- 3.State trade-offs and the 10x plan
- 4.Define the APIs
- 5.Deep-dive into the hardest parts
- 6.Estimate traffic, storage and bandwidth
- 7.Separate functional and non-functional requirements
- 8.Design the data model
Next episode: Karim's investor deck says "ready for 10× growth", and he wants proof on one page by Friday: requests, storage, servers and cost.
Mark it complete to update your skill map.
Go deeper (optional)You don't need this to understand the lesson. Open it when a project needs the details: Technical deep dive, Interactive simulation, Visual diagram, Analogy, Check your understanding, Practice ladder, Explain it yourself.
Technical deep dive
Steps 4 and 5 deserve real artefacts, even rough ones. Writing the API and the table exposes missing requirements faster than any diagram. Here's Lina's full pass, written as the design doc she'd share with the team.
Lina designs StayHub reviews
Maya wants guest reviews (rating, text, photos, hotel replies) on every hotel page within four weeks. StayHub: ~30,000 guests/day, ~2,500 bookings/day, 1,800 hotels, one modular monolith on PostgreSQL. Design it, and plan for Karim's 10× growth.
- 1Pin down the requirements
Functional: post a review (finished stays only, one per booking), list reviews (newest, best, worst), rating summary, hotel replies, moderation. Non-functional: reviews part of the hotel page p99 < 200 ms, a submitted review is never lost, new reviews visible within minutes.
Why: The non-functional list decides the architecture. "Visible within minutes" is what later allows a background job and a cache.
- 2Estimate, and say what each number changes
Page views ~120k/day → ~1.4 reads/s avg, ~15/s at peak (×10 at 10× growth: 150/s). Reviews ~750/day → 0.01 writes/s. Text ~1 KB → ~270 MB/year. Photos ~500 KB → ~135 GB/year.
textreads: 120,000 views/day ÷ 86,400 s ≈ 1.4/s → peak ×10 ≈ 15/s writes: 2,500 bookings × 30% = 750/day ≈ 0.01/s text: 750 × 1 KB × 365 ≈ 270 MB/year → one table, no sharding photos: 750 × 500 KB × 365 ≈ 135 GB/year → object storage + CDNWhy: An estimate that doesn't change the design is decoration. These rule out new databases and send photo bytes away from the app servers.
- 3Write the API and the table
GET /hotels/{id}/reviews?sort=&cursor=,GET /hotels/{id}/rating-summary,POST /reviewswith an idempotency key, answering202 pending_moderation. Table:reviews(id, hotel_id, booking_id UNIQUE, rating CHECK 1–10, body, status, created_at)with an index on(hotel_id, status, created_at DESC).Why:
booking_id UNIQUEturns "one review per stay" into a rule the database enforces. The index matches the hottest query exactly. - 4Draw only the boxes the numbers ask for
A reviews module inside the existing app, the existing PostgreSQL, a summary row per hotel cached in Redis, photos uploaded to object storage and served by the CDN, moderation as a background job fed by the outbox.
Why: Every box points back to a requirement: Redis to the 200 ms target, the CDN to photo bandwidth, the job to "never lost" plus "within minutes".
- 5Deep-dive the hot read path
Computing
AVG(rating)over 3,000 reviews per page view costs ~90 ms. Instead, the moderation job updates arating_summaryrow when a review is approved and deletes its Redis key. Reads hit Redis (~2 ms) or the row (~6 ms). Pagination uses a cursor on(created_at, id), notOFFSET.Why: This path runs on every hotel page and has an explicit target. Submission runs once every two minutes and needs no cleverness.
- 6Name the trade-offs and the trigger
Summaries can lag by a few minutes. We chose PostgreSQL over a document store because "one review per booking" is a relational rule. Revisit if writes pass ~50/s, or reviews need their own deploys.
Why: Every design gives something up. Writing the trigger down means nobody has to guess later when the design stopped fitting.
| Step | Output | Question it answers |
|---|---|---|
| 1–2 Requirements | Functional + non-functional list, scope | What are we building, and how good must it be? |
| 3 Estimates | QPS (avg/peak), storage, bandwidth | How big is it, really? |
| 4 API | Endpoints with inputs/outputs | What is the contract? |
| 5 Data model | Tables, keys, indexes, store choice | What do we store and how is it read? |
| 6 High-level design | Component diagram | What are the parts and how do they talk? |
| 7 Deep dives | 1–3 detailed sub-designs | Where will it break first? |
| 8 Trade-offs | Choices, alternatives, 10× plan | What did we give up, and when do we revisit? |
Interactive simulation
Visual diagram
Step 6 output for the hotel reviews service. Every box is justified by a requirement or an estimate from earlier steps.
Analogy
- Designing a houseHow many people live here?Designing a systemRequirements
- Designing a houseMust stay warm at −10 °CDesigning a systemNon-functional targets
- Designing a house5 people, 2 showers each, heater sizeDesigning a systemEstimates
- Designing a houseDoors, windows and socketsDesigning a systemThe API
- Designing a houseThe floor planDesigning a systemThe high-level design
- Designing a houseStairs and foundations, checked closelyDesigning a systemDeep dives
Where the analogy breaks: a house is built once. A system keeps growing, so you also say what would change at ten times the load.
Check your understanding
Practice ladder
Follow the numbers: wishlists
Scenario Warm-upMaya wants wishlists: guests save hotels to a list and see it on any device. A teammate's design: a separate wishlist service, a Cassandra cluster, and Kafka to sync saves between devices.
The numbers: - ~30,000 guests visit a day; about 8% save at least one hotel, 3 saves each on average. - Each saved row is ~100 bytes. The wishlist page is opened ~6,000 times a day, peaking at 10× the average in the evening. - Requirement: a save shows up on the guest's other device within a few seconds.
Trace the method's steps 3 to 6: compute writes/s, peak reads/s and storage per year, then say which of the teammate's boxes the numbers justify.
Finish Lina's price-alert doc
Design PracticeGuests save a hotel, dates and a target price, and get an email when the price drops below it. Lina's design doc has two empty steps. Fill in step 3 and step 7.
- Clarify: alert on (hotel, check-in, nights, max price). Email only. Expires after check-in.
- Requirements: create/list/delete alerts; notify once per drop; within 30 minutes; low cost.
- Estimate: ___ (StayHub has 400,000 accounts; Maya expects 10% to create alerts, 3 each. Prices for 1,800 hotels × ~90 date combinations are refreshed every 15 minutes; usually ~2% of them change.)
- API:
POST /alerts,GET /alerts,DELETE /alerts/{id}. - Data:
alerts(id, guest_id, hotel_id, check_in, nights, max_price, last_notified_price), index on(hotel_id, check_in, nights, max_price). - High-level: price refresh → keep only changed prices → background job matches alerts → email job.
- Deep dive: ___
- Trade-offs: up to 15 minutes of delay, well within 30; one PostgreSQL table.
Partner photo uploads
Design PracticeThe Hotel Partners team wants hotels to upload their own photos (up to 40 per hotel, ~2 MB each from phones) and see them on the hotel page after a quick check for bad content.
StayHub has 1,800 partner hotels today; Karim's plan says 10× in two years. Hotel pages are viewed ~120,000 times a day, each showing ~8 photos.
Run the method end to end, a few lines per step. Make at least two of your estimates change a design decision.
"12 people are looking at this hotel"
Decision StretchMaya wants a live counter on hotel pages: "12 people are looking at this hotel right now". She thinks it will lift bookings.
Options on the table: - A: a WebSocket from every hotel page to new realtime servers, pushing exact counts every second. - B: each page view sets a short-lived key in Redis (viewing:{hotel}:{guest}, expires after 5 minutes); the page shows a count read on page load, refreshed every 30 s.
Facts: ~15 hotel page reads/s at peak today, ~150 at 10×. The team has no realtime servers yet. Maya says "roughly right is fine".
Write your decision as: "We chose X over Y because…, accepting the risk of…", and point to the requirement that decided it.
Explain it yourself
Explain the system design method to a junior engineer preparing for their first design review, and why the order of steps matters.