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:

Requirementswhat, and how wellRough numbershow bigAPIwhat callers askData modelwhat is storedDiagramsimplest that worksBottleneckswhat breaks firstRequirementswhat, and how wellRough numbershow bigAPIwhat callers askData modelwhat is storedDiagramsimplest that worksBottleneckswhat breaks first

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.
Viewing the seat map
3,300 /s
Buying seats
17 /s

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

http
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 ago

This 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:

BrowserTicket serviceDatabasesee seats, buyread, updateBrowserTicket serviceDatabasesee seats, buyread, update

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.
BrowserWaiting lineLoad balancerTicket serviceseveral copiesSeat map cachea few seconds oldDatabasethe truth about seatsa few at a timeviewingbuyingBrowserWaiting lineLoad balancerTicket serviceseveral copiesSeat map cachea few seconds oldDatabasethe truth about seatsa few at a timeviewingbuying

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

1. Which of these is a non-functional requirement for the ticket site?
2. The site serves 3,300 seat-map views and 17 purchases a second. What follows?
3. Your first diagram is one service and one database. A teammate wants to add a cache, a queue and a search index "to be safe". What is the best reply?
4. Two fans press Pay for seat A12 at the same moment. What must the design guarantee?
5. Should an online shop's checkout and a stadium ticket sale both hold what the buyer picked while they pay?

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.