System designCore11 min
How to approach a system design
A blank page and a one-line request. Here is the order of questions that turns "build us a ticket site" into a design you can defend, one decision at a time.
1 · The trap
The first box you draw is usually the wrong one
"We need a site to sell tickets for our concerts." Most people reach for the whiteboard at once: a database here, a cache there, a load balancer on top. Then the questions start. Which database? How many servers? Does it need a cache at all?
None of those has an answer yet, because nobody has said what the site must do or how big it must be. A design is a set of answers. This page is about asking the questions first, in an order that makes each answer easy:
Six steps from a blank page to a design. The order is a guide, not a law: you will loop back as you learn more, and that is normal.
One example runs through this page: a website that sells concert tickets. Buying a ticket looks like one simple form, but behind it are most of the hard questions in system design: thousands of fans arriving at once, two of them wanting the same seat, and payments that must never be lost.
2 · Step 1
Start with what it must do, and what it will not
Functional requirements are the things the system does for its users. Write them as plain sentences that start with "A user can…", and keep the list short. For the ticket site:
- A fan can see which seats are still free, and their prices.
- A fan can choose seats and pay for them.
- A fan receives the tickets by email.
Just as important is what you leave out, and saying so:
In scope | Out of scope, for now |
|---|---|
See free seats | Reselling tickets between fans |
Choose and pay | Recommendations for other concerts |
Tickets by email | A mobile app |
Why list what is out? Because every feature you stay quiet about is one somebody assumes is in. A design that tries to cover everything covers nothing well. Three features done properly beat ten sketched.
3 · Step 2
How well it must work decides the design
Non-functional requirements say how well the system must do its job: how fast, how much, how often it may be down. You will also hear them called quality attributes. The features above fit in three lines. These are what make a design easy or hard:
Quality | The question | For the ticket site |
|---|---|---|
Scale | How much traffic and data? | 200,000 fans arrive in the first 10 minutes of a sale |
Latency | How fast must it answer? | The seat map loads in under a second |
Consistency | May two users see different data? | A seat is never sold to two fans |
Durability | May stored data ever be lost? | Never lose a paid ticket |
Availability | How often may it be down? | Never during a sale: that is when the money comes in |
Cost | What may it cost to run? | Busy for minutes, quiet for weeks: pay for the rush only when it happens |
Two rules make these useful:
- Put a number on it. "Fast" is a wish. "The seat map loads in under a second" is a requirement you can design for and test.
- Pick the few that matter. Every system wants all six. Ask which ones would sink this product if missed. Here it is consistency and scale: selling one seat twice, or falling over when the sale opens, is a front-page failure.
To see how much these decide, keep the three features and change only the numbers:
What changes | A 300-seat venue | A stadium tour |
|---|---|---|
Traffic | A few fans a minute, over weeks | 200,000 fans in the first minutes |
Two fans want the same seat | Rare, and easy to sort out by phone | Happens every second of the sale |
A reasonable design | One server, one database | Many servers, a waiting line, careful seat locking |
Same features, two very different systems. That is why you ask before you draw. Each quality has its own page later in this track: Latency, Availability & reliability, Consistency models.
4 · Step 3
A rough number tells you which problem you have
You do not need exact figures. You need to know whether you are dealing with tens, thousands or millions, because each calls for a different design. Take the stadium tour:
- Looking. 200,000 fans arrive in the first 10 minutes and each refreshes the seat map about 10 times: 2,000,000 views in 600 seconds, about 3,300 reads a second.
- Buying. 20,000 seats, about two per order, sold out in those same 10 minutes: 10,000 orders ÷ 600 seconds, about 17 orders a second.
About 200 views for every purchase. The numbers split the site into two very different problems.
That split has already made decisions for you:
- Viewing is huge and forgiving. A seat map that is a few seconds old is fine, so it can be served from memory, copied as widely as needed.
- Buying is small and unforgiving. Only 17 a second, but each one must be exactly right: a seat changes hands once.
Two paths, two designs. Estimation, capacity & cost takes the numbers further: storage, machines and the monthly bill.
5 · Steps 4 and 5
Decide what callers can ask before deciding what is inside
The API is the list of things the outside world can ask your system to do. Writing it down turns the requirements into something exact. It is small: roughly one call per functional requirement.
A simplified API
GET /events/42/seats
→ 200 OK
[ { "seat": "A12", "price": 80, "free": true }, … ]
POST /events/42/orders
{ "seats": ["A12", "A13"], "payment": "…" }
→ 201 Created { "order": "o-981", "tickets": ["A12", "A13"] }
→ 409 Conflict a seat was sold a moment agoThis API is kept deliberately small so the idea is easy to follow. A real ticket site needs more: sign-in, holding seats while a fan pays, a place in the waiting line, and payment that can take more than one step.
Notice the second answer to the order. Writing the API forces the awkward case into the open: two fans press Pay for the same seat, and one of them must be told no.
The data model is what the system must remember in order to answer those calls:
Record | What it holds | How it is used |
|---|---|---|
Event | Artist, venue, date | Read constantly, almost never changed |
Seat | Event, seat number, price, free or sold | Read by everyone; changed once, by one buyer |
Order | Fan's email, seats, payment, time | Written once per purchase, kept for years |
Look at the Seat record: thousands of fans read it, and many try to change it at the same instant. That one record is the heart of the whole design. API first or data model first? Either; they shape each other. What matters is that both exist before the diagram.
6 · Step 6
Draw the simplest thing that works, then find where it breaks
Start with the smallest design that meets the functional requirements, and nothing more:
Correct, and enough for the 300-seat venue. Every feature works.
Now hold each non-functional requirement against it and ask where it fails:
- 3,300 seat-map views a second. Every view hits the database, which also has to handle the purchases. Serve the seat map from a cache in memory, refreshed every few seconds.
- 200,000 fans at once. Even with a cache, letting everyone in at the same moment is a stampede. Put them in a waiting line and let them in a few thousand at a time.
Two more are about correctness and staying up:
- Never sell a seat twice. The purchase must check that the seat is free and mark it sold in one indivisible step, in one place that is the single source of truth.
- Never down during a sale. Run several copies of the service behind a load balancer.
Each new part is there because a requirement forced it. Viewing goes to the cache; buying always goes to the one place that knows the truth.
This is the habit to keep: start simple, and add a part only when a number demands it. If you cannot name the requirement a box serves, remove the box. Caches, load balancers and queues each get their own pages; the skill here is knowing why each one is in the picture.
7 · The rule
There is no best design, only a best fit
Every choice buys something and costs something, and the requirements decide which price is worth paying. One decision shows it: when a buyer picks something, do you hold it while they pay?
Choice | What you gain | What you pay |
|---|---|---|
Hold it for 10 minutes, as a ticket site does | Nobody loses a seat after typing in a card number | Seats sit locked by fans who never pay, and others see them taken |
No hold: first to pay wins, as many online shops do | Nothing is ever locked by someone who walked away | Buyers lose what they picked at the last step, again and again, in a rush |
Neither is right on its own. Anything sold by the seat holds it: a seat is one of a kind, and losing it at the payment step would enrage thousands of fans in the stadium rush, so ticket sites hold seats, with a short timer. Many online shops don't hold an item in a cart: carts are abandoned all the time, holding stock for them would show items as sold out that are still for sale, and there is usually another one on the shelf. The requirements decided, which is the point of writing them down.
The same pattern repeats in every design:
- Consistency or speed. Checking the one true copy is exact and slower; a copy in memory is fast and a little stale.
- Cost or performance. More machines make the rush smoother, and the bill larger.
- Simple or flexible. A design that handles every future need is harder to build and to run today.
State a trade-off in one sentence with three parts: what you chose, which requirement it serves, and what it costs. "Hold seats for 10 minutes, because fans must not lose them at payment, at the cost of seats locked by people who leave." If you cannot fill in the third part, you have not found the trade-off yet.
Check yourself
5 questions
Takeaways
Remember this
- Ask before you draw: requirements, rough numbers, API, data model, diagram, bottlenecks.
- Functional requirements say what the system does; non-functional ones say how well, and they decide the design.
- Put a number on every quality, and pick the few that would sink the product if missed.
- Start with the simplest design that works, and add a part only when a requirement forces it.
- There is no best design, only a best fit: say what you chose, which requirement it serves, and what it costs.