The six-step delivery framework for system design interviews
The interview routine for any design problem: a fixed order so you always end with a working system. What to do in each step, how long to spend, and the mistakes that cost the most.
On this page
A design problem is open-ended, and an open-ended problem is easy to drown in. You can spend twenty minutes on one perfect cache and never draw the database.
Crux: follow the same steps in the same order, so that you always reach a working end-to-end design before you start improving it.
Why use a framework
The most common way to fail a design discussion is not a wrong answer. It is not finishing. Time runs out while the design is still half-drawn, or while you are deep in a detail nobody asked for.
A framework fixes that. It tells you what to do next, so you spend your attention on the problem and not on deciding where to start.
These timings suit a session of about 35 to 45 minutes. They are a guide, not a rule. If you are in an interview, confirm the format with the interviewer or recruiter, since some expect more time on one step than another.
Requirements
Goal: agree on what you are building before you design anything. About 5 minutes.
Functional requirements
These are the core features of your system, and they should be the first thing you discuss. Write them as "Users should be able to…" and ask targeted questions to reach a prioritised list: "does the system need to do X?", "what would happen if Y?" Keep your requirements targeted. A long list looks thorough but leaves no time to design any of it. About three is right.
For a link shortener:
- Users can create a short link for a long URL.
- Anyone can open a short link and land on the original page.
- Users can see how often their link was opened.
Also say what is out of scope. For the link shortener: analytics dashboards, custom domains and abuse detection. Say that these are not in scope for now and are left for later parts, and that you will come back to them at the end (step 6). Naming them keeps the list short, and shows you left things out on purpose.
Non-functional requirements
These are how well the system must do it. Write them as "The system should…" and put a number on each one. "Low latency" is vague. "A redirect completes in under 100 ms" is something you can design for. Pick three to five from this checklist, the ones that matter for this problem.
- CAP theorem: in a distributed system, partition tolerance is a must, because networks do fail. So the real choice is between availability and consistency: when the network splits, should the system stay available and risk stale answers, or stay consistent and refuse some requests?
- Environment constraints: where does it run? A phone with limited battery, memory or bandwidth, such as video on a slow connection, changes the design.
- Scalability: all systems must scale, but does this one have unusual needs? Bursty traffic at a certain hour, spikes on holidays, and the read-to-write ratio all matter, since reads and writes scale differently.
- Latency: how quickly must it respond, especially for requests that need real computation? Low-latency search in a Yelp-like product is an example.
- Durability: how bad is losing data? A social network can tolerate a little loss. A bank cannot.
- Security: how secure must it be? Think of data protection, access control and compliance.
- Fault tolerance: how well must it survive failures? Think of redundancy, failover and recovery.
- Compliance: are there legal or regulatory rules or industry standards to meet?
Name the hard part
Before you leave requirements, say which one will be hardest. For the link shortener it is generating unique short codes at scale. Everything in the deep dives will come back to it, so naming it early tells the other person where you are heading.
Capacity estimation
Usually skip it up front. Say you will do the maths when it affects a decision, for example whether one machine can hold the data or you need to shard. Look for the few numbers that can change the design:
- Requests per second at peak, not the daily average.
- The read-to-write ratio, because reads and writes scale differently.
- How much data you store in a year, and whether it fits on one machine.
- The size of one record, which turns counts into storage.
- The latency budget, which decides whether a cache is optional or required.
If none of these changes a decision, skip the maths. The lesson on mastering estimation covers how.
Core entities and database schema
Goal: name the entities, sketch the domain model, then choose the database and write its schema. About 5 minutes.
Ask two questions:
- Who are the actors in the system? Do they overlap?
- What are the nouns or resources needed to satisfy the functional requirements?
For the link shortener: User and Link. Add Click if each click must be stored.
Say it is a first draft and you will add to it. Choose clear names, because names are part of the design.
Sketch the domain model
Now give each entity a few fields, and note how the entities relate. This is your domain model. Keep only the fields the requirements need at this point:
Choose the database
Decide now, because the schema depends on it. Let the access patterns from your requirements decide, not habit:
- Relationships, transactions and joins point to a relational database. This is the default for product design.
- Simple key lookups at huge scale, a flexible shape or very heavy writes point to a NoSQL database such as DynamoDB or Cassandra.
Do not argue "SQL vs NoSQL" in general. Name the property you need.
For the link shortener the main access is "find the URL for a code", which is a key lookup, plus "list a user's links". There are few relationships, and a code must be unique. Decision: a relational database such as Postgres, because a primary key gives us uniqueness, transactions are simple, and the link table fits on one machine for years (see the numbers in the estimation lesson). Cost: if click writes outgrow it, clicks move to a write-optimised store. We note that now and return to it in the deep dives.
Write the schema
List the tables with their keys and the indexes your queries need, and only the fields the requirements need:
links
code text primary key
url text
owner_id text indexed (to list a user's links)
clicks
code text indexed
clicked_at timestamp
The primary key on code does real work: it is what guarantees two links never share a code. You will come back to this schema in later steps, adding a field when a step asks for one (for example a creation time once the design needs it), and moving things around if the design changes.
API
Goal: define the contract between the system and its users. About 5 minutes.
Each functional requirement usually becomes one or two endpoints. First pick the style:
- REST (Representational State Transfer) uses HTTP verbs (GET, POST, PUT, DELETE) to create, read, update and delete resources. This is your default for most interviews.
- GraphQL lets the client ask for exactly the data it wants, which avoids fetching too much or too little. Choose it when you have diverse clients with different data needs.
- RPC (Remote Procedure Call, for example gRPC) is action-oriented and faster than REST between services. Use it for internal APIs where performance is critical.
Do not overthink this. Default to REST unless you have a specific reason not to. If you need real-time features, you will also need WebSockets or server-sent events, but design the core API first.
For the link shortener we choose REST and build the endpoints from the core entities. Resources are plural nouns that represent things in your system:
POST /v1/links
body: { "url": string }
GET /{code}
-> redirect to the original URL
GET /v1/links/{code}/stats
-> { "clicks": number }
The redirect lives at the root so that short links stay short.
Never take "who is calling" from the request body. The caller's identity comes from the authentication token. If the body says user_id, anyone can claim to be anyone.
Optional: data flow
Goal: for systems that process data in stages, list the steps in order. About 5 minutes, and only when it applies.
Data flow is the sequence of actions that turns an input into an output. When the system is a pipeline, writing it down before you draw boxes gives the high-level design a skeleton: each step will become a component or an arrow.
Use this step when data passes through several stages. Skip it when the system is mostly simple reads and writes. A link shortener is simple, but its two flows make a small example:
It matters most in pipelines. A web crawler, for example: take a starting URL, fetch the page, parse the HTML, extract new URLs, store the data, repeat. Even for a simple system, writing the list can show you a step you would have forgotten, such as recording the click.
High-level design
Goal: draw the simplest architecture that satisfies the API and the functional requirements. The longest step, 10 to 15 minutes.
Work one endpoint at a time. Start at the request, follow it through your boxes, say what changes in the data, and end at the response.
Four habits make this step go well:
- Narrate while you draw. Silence makes it hard for the other person to follow or help.
- Write down only the fields that matter. For a link, that is the code, the URL, the owner and a creation time. Skip obvious columns and types.
- Revisit the schema as you draw. Add the fields and indexes a database needs as you meet them (the diagram adds a creation time to the link), and keep it short. You never design the model in isolation.
- Name complexity and defer it. Say "this read will need a cache, I'll come back to it" and keep going. Adding a cache, a queue and replicas now is how designs stay unfinished.
Deep dives
Goal: harden the design, starting with the hard part you named in step 1, and show how your design solves it. About 10 minutes.
Go back to the numbers you wrote in step one and ask whether the design meets them.
Start with the hard part
In step one we said the hard part is generating unique short codes at scale. Open the deep dives here, and show how the design you have already drawn solves it:
- The problem: many servers create links at once, and two links must never get the same code.
- Our design: the service generates a random code and inserts the row. The primary key on
code(from the schema in step 2) rejects a duplicate. On a conflict, the service picks a new code and tries again. - Why it works: servers never have to talk to each other, so it scales out.
- What it costs: an occasional retry, and more collisions as the table fills up.
Make each trade-off explicit
Give every deep dive the same shape: the options, your pick, why, and what it costs. For the short codes:
- Option A: a counter, encoded into a short string. No collisions, but one counter is a bottleneck.
- Option B: random codes. No coordination needed, but you must handle a collision.
- Pick: B with a uniqueness check, because it scales without coordination, at the cost of an occasional retry.
The reasoning matters more than the pick. A different pick with a clear reason is a good answer.
Other deep dives
Then take the other weak points, in order of how much they matter:
- Redirects in under 100 ms at high traffic: add a cache for hot codes.
- The click counter is written far more than read: can it be counted asynchronously, or moved to a write-optimised store?
Who leads
How much you drive this step depends on seniority. A more junior answer expects the interviewer to point at weak spots. A more senior one finds them first and leads. Either way, do not talk over the person you are talking to. Their question is often a signal about where the real problem is.
Recap, trade-offs and improvements
Goal: tie the design together, say what you gave up, and say what you would do next. About 5 minutes.
Recap the requirements
For each non-functional requirement from step one, say in one sentence how the design meets it:
- Redirects under 100 ms: hot codes come from a cache, and the lookup is a primary-key read.
- Codes never collide: the primary key rejects a duplicate, and the service retries with a new code.
- Click counts keep up with traffic: clicks are written asynchronously, so they never slow a redirect.
State the trade-offs
Say every trade-off in one shape: "we chose X over Y because Z, at the cost of W."
Name what is most likely to fail
Pick one thing, and say how you would notice: an alert, a dashboard, a failing health check. For the link shortener it is the click pipeline falling behind, and the signal is a growing queue. This is the habit behind Site Reliability Engineering.
Potential improvements
This is where everything you left out comes back, so nothing looks forgotten. Pick the few that matter most and say what each would give:
- Clicks in a write-optimised store, with pre-aggregated counts, if click volume outgrows the database.
- The "not in scope" items from step 1: custom domains, analytics dashboards, abuse detection.
- More than one region, for lower latency and better availability.
You do not build them. You show that you know what comes next and why.
How every breakdown uses this framework
From here on, each system in this course is worked through with the same headings, so you can compare any two designs side by side:
- Requirements: functional, non-functional, and out of scope ("not in scope for now").
- The hard part: the one thing that makes this system tricky.
- Core entities and database schema: the domain model, the database decision and the schema.
- API
- Data flow, when the system moves data through stages.
- High-level design, one endpoint at a time.
- Deep dives, starting with the hard part and how the design solves it, each with an explicit trade-off.
- Recap, trade-offs and improvements: how the design meets the requirements, what was given up, what is most likely to fail, and what you would do next, including the out-of-scope items.
Common mistakes
- Writing ten requirements and designing none of them.
- Spending the first ten minutes on estimation that changes nothing.
- Listing every column of every table.
- Adding a cache, a queue and sharding before the basic path works.
- Letting the caller's identity come from the request body.
- Drawing in silence.
Check yourself
- Why is "the system should be fast" a weak non-functional requirement, and how would you improve it?
- In a distributed system, which part of CAP is a given, and what is the real choice?
- When is the data flow step worth doing, and when do you skip it?
- How do you choose the database, and what goes into the schema?
- Where does the caller's identity come from in an API?
- Why defer the cache while drawing the high-level design?
- Why name what is out of scope?
- Which problem does the first deep dive cover, and how does the schema help solve it?
- What shape should every trade-off take in a deep dive?
- What goes into the final step, and where do the "not in scope" items come back?
- Which numbers are worth estimating, and when do you skip estimation?