You all might have watched a lot of YouTube videos on system design and heard almost every one of them say:
“Before you start designing, clarify the functional and non-functional requirements.”
And yes, obviously.
You’re supposed to clarify the requirements.
But how do you actually do that?
What exactly do you ask the interviewer?
How do you know whether the question you’re asking is useful?
How do you go from:
“Design a URL shortener”
to actually knowing what you’re supposed to build?
I think this is one of those things that sounds very obvious when someone explains it after the fact, but when you’re actually sitting in an interview, it’s much harder.
So I wanted to break down the five things we usually hear about:
1-Functional and non-functional requirements
2-API design
3-High-level design|
4-Database design
5-Detailed design
But not as a checklist.
More importantly, how do you arrive at each of these? What questions do you ask, and why?
First: Don’t Start Designing
The interviewer says:
“Design a URL shortener.”
My brain might immediately go:
“Okay, Redis, database, load balancer…”
But wait.
I don’t even know what I’m designing yet.
The problem statement is intentionally vague.
So the first thing I’m actually trying to do is:
remove ambiguity.
I want to turn:
“Design a URL shortener”
into something more like:
“We’re building a URL-shortening service for authenticated users, supporting URL creation and redirection, with X traffic, Y latency requirements, and Z availability requirements.”
Now I have something I can actually design.
And this is where those five things come in.
- Functional Requirements: What Does the User Actually Need to Do?
The usual advice is:
“Clarify the functional requirements.”
Okay.
But I don’t want to ask the interviewer:
“What are the functional requirements?”
That’s technically correct, but it doesn’t really help me think.
I’d rather start with:
Who is using the system?
For a URL shortener:
“Are users authenticated, or can anyone create a short URL anonymously?”
That one question already changes things.
If users are authenticated, I might need:
user accounts
ownership
authorization
URL management
user-specific limits
If it’s anonymous, a lot of that disappears.
So I’m not asking questions just to collect information.
I’m asking:
“What information could actually change my design?”
Then ask: What can the user actually do?
For example:
“Is the requirement only to create and redirect short URLs, or do we also need expiration, custom aliases, analytics, deletion, etc.?”
Because “URL shortener” could mean a very simple service.
Or it could mean:
create URLs
delete URLs
custom aliases
expiration
analytics
user dashboard
authentication
I need to know where the boundary is.
And this applies to every system.
If someone says:
“Design Instagram.”
That could mean the entire Instagram product.
Obviously we’re not designing all of Instagram in an interview.
So I need to establish the scope.
Find the Main User Journey
Once I know the scope, I want to understand the most important thing the user is trying to do.
For a URL shortener:
User
↓
Submits long URL
↓
System generates short code
↓
User gets short URL
↓
Someone opens short URL
↓
System finds original URL
↓
User is redirected
That’s the core flow.
Now I have something around which I can start thinking.
And this gives me a useful rule:
Don’t try to understand every possible feature first. Find the primary user journey.
- Non-Functional Requirements: How Well Does It Need to Work?
Okay, now I know what the system does.
The next question is:
How well does it need to do it?
This is where non-functional requirements come in.
And again, I don’t want to ask:
“What are the NFRs?”
I want to ask questions that reveal the constraints.
How much traffic?
Suppose the interviewer says:
“We have millions of users.”
Cool.
But that doesn’t tell me much.
Millions of users doesn’t mean millions of requests per second.
So I’d ask:
“How many daily active users are we expecting?”
Then:
“Approximately how many requests per second?”
Then:
“What’s the read-to-write ratio?”
And:
“Is that average traffic or peak traffic?”
Suppose I get:
1,000 URL creations/sec
100,000 redirects/sec
Now I know something important.
The system is very read-heavy.
That immediately makes things like caching and read scaling relevant.
Notice what happened:
I didn’t decide:
“Let’s use Redis.”
The traffic requirement led me toward that discussion.
What About Latency?
Instead of vaguely asking:
“What’s the latency requirement?”
I can ask something more concrete:
“For the redirect operation, are we okay with something like 100 ms, or do we need it to be closer to tens of milliseconds?”
Now I can start thinking about what belongs in the critical path.
If something is expensive but doesn’t need to happen before the user receives a response, maybe it can happen asynchronously.
Again:
Requirement → consequence → architecture.
What Absolutely Needs to Work?
Availability is another thing I don’t want to treat as a number in isolation.
I can ask:
“If the analytics system is temporarily unavailable, should URL redirection still work?”
If the interviewer says yes, then I’ve learned something important:
Redirection is critical. Analytics isn’t.
So maybe analytics shouldn’t be part of the synchronous redirect path.
Maybe I can process analytics asynchronously.
That’s a much more useful way of thinking about availability than simply memorizing:
“The system should have 99.99% availability.”
And Then There’s Consistency
One question I like here is:
“What happens if two users temporarily see different values?”
Because not everything needs the same consistency guarantees.
A social-media like count being slightly stale might be acceptable.
A bank balance being wrong is a very different problem.
So instead of memorizing:
“This uses eventual consistency and this uses strong consistency.”
Ask:
“What happens if the data is temporarily inconsistent?”
The answer tells me how strong my consistency requirements actually need to be.
- API Design: Don’t Just Design Endpoints
Now that I understand what the system needs to do, I can think about:
How does the outside world interact with it?
Suppose I need to create a short URL.
First ask:
“What does the client need to send?”
Probably:
{
"url": "https://example.com"
}
Then:
“What does the client need back?”
Maybe:
{
"shortUrl": "https://short.ly/abc123"
}
Only now does something like:
POST /urls
start making sense.
I’m not inventing the endpoint.
I’m deriving it from the user action.
But Don’t Stop at the Happy Path
For every important API, I want to ask:
What is the action?
Create a short URL.
What information does it need?
The original URL.
What does it return?
The generated short URL.
What can go wrong?
Invalid URL
Rate limit
Duplicate request
Service failure
And then one of my favourite questions:
What happens if the client retries?
Imagine this:
Become a Medium member
The client sends a request.
The server successfully creates the short URL.
But the response gets lost.
The client doesn’t know whether the request succeeded.
So it sends the request again.
What happens?
Do we create another short URL?
Or do we return the previous result?
Now we’ve naturally arrived at:
idempotency.
This is why I don’t think API design should just be:
“Let’s design some REST endpoints.”
The API is where the system’s behaviour starts becoming explicit.
- HLD: Stop Drawing Boxes Just Because You Know Them
This is probably where I’ve seen the most “system design interview diagrams.”
Something like:
Client
↓
Load Balancer
↓
API Gateway
↓
Microservices
↓
Redis
↓
Kafka
↓
Database
And then everyone nods because it looks like a system.
But the interviewer can simply ask:
“Why do you need Kafka?”
And suddenly we’re in trouble.
So I’ve started thinking about HLD differently.
Before adding a component, ask:
What problem does this component solve?
Example: Why would I need a queue?
Suppose I’m designing a video-sharing platform.
Video processing can take a long time.
I don’t want the user to sit there waiting for:
transcoding
generating different resolutions
thumbnails
metadata processing
So I might design:
Upload
↓
Object Storage
↓
Create Processing Job
↓
Queue
↓
Workers
↓
Processed Videos
Now the queue has a reason to exist.
It’s not there because:
“Kafka is something senior engineers use.”
It’s there because:
I have expensive work that doesn’t need to block the user’s request.
That’s the kind of explanation I want to be able to give.
Every Box Should Have a “Why”
If I draw Redis:
Why?
Because the data is frequently read and can be cached.
If I draw Kafka:
Why?
Because work can happen asynchronously and doesn’t need to block the request.
If I draw a separate service:
Why?
Because it has a separate responsibility, scaling requirement, or failure boundary.
If I can’t explain why a component exists, maybe I shouldn’t have drawn it.
So my rule here is:
Architecture should be the consequence of requirements, not a collection of technologies I remember.
- Database Design: Don’t Start With “SQL or NoSQL?”
This is another place where I think it’s easy to jump to the wrong question.
Instead of:
“Should I use PostgreSQL or MongoDB?”
Start with:
What information does the system actually need to remember?
For a URL shortener:
original URL
short code
user
creation time
expiration time
Then ask:
How do I need to access this information?
Maybe the most important query is:
shortCode → originalURL
That’s more useful to me than immediately debating databases.
Then I can ask:
How often do we run this query?
How much data will we have?
How quickly will it grow?
What relationships exist?
Do we need transactions?
What consistency do we need?
Only after answering these questions should I start thinking about the storage technology.
So the mental sequence becomes:
Data → relationships → queries → query frequency → scale → consistency → storage choice
Not:
Problem → MongoDB because it’s scalable.
- Detailed Design: Where Is the Interesting Problem?
At this point, I should know:
what we’re building
who uses it
how much traffic we have
what the important APIs are
what the major components are
what data we store
Now I can zoom in.
But I don’t need to explain every single class or function.
Instead, ask:
What’s the hardest part of this system?
For a URL shortener, maybe it’s:
“How do we generate unique short codes when multiple servers are creating URLs at the same time?”
Now we can go deeper.
Request
↓
Generate unique ID
↓
Base62 encoding
↓
Short code
↓
Store mapping
And now more questions appear:
What if two servers generate the same ID?
Can IDs be generated independently?
Do we need a centralized ID generator?
What happens if the ID generator goes down?
What happens if two identical requests arrive simultaneously?
Now I’m actually doing detailed system design.
The Most Important Thing: Every Answer Should Create the Next Question
This is probably the biggest thing I’m taking away from all of this.
System design shouldn’t feel like:
Question → answer → next section.
It should feel like:
Question → answer → consequence → next question → design decision
For example:
The interviewer says:
“We expect 1 million requests per second.”
I ask:
“Are those mostly reads or writes?”
They say:
“Mostly reads.”
I ask:
“Can the data be cached?”
They say:
“Yes, it doesn’t change frequently.”
Now I think:
“Okay, caching can reduce the load on the database.”
Then I ask:
“What happens on a cache miss?”
Now I’m thinking about the database.
Then:
“Can the database handle the remaining traffic?”
If not:
“Can we use read replicas?”
Notice how the architecture is slowly appearing.
I didn’t start with:
“Let’s use Redis.”
The requirement led me there.
So What Are the Five Things Actually Doing?
Maybe this is the easiest way for me to remember them.
- Functional requirements
What are we building?
Who uses it?
What can they do?
What is the main user journey?
What’s in scope?
What’s out of scope?
- Non-functional requirements
How well does it need to work?
How much traffic?
How much data?
What latency?
What availability?
What consistency?
What happens during failures?
- API design
How does the outside world interact with it?
What does the client send?
What does it receive?
What can go wrong?
What happens on retries?
Is it idempotent?
- HLD
What pieces do I need, and why?
Cache?
Queue?
Database?
Workers?
Services?
Load balancer?
And most importantly:
Why?
- Database + detailed design
How does the system actually store and process information?
What are the entities?
What are the important queries?
What happens under concurrency?
What happens when something fails?
Where is the bottleneck?
The Question I Want to Keep Beside Me
I don’t think I need to memorize a giant list of system-design questions.
I just need to keep asking:
“What do I need to know before I can make this decision?”
I need to choose a database?
First understand the data and access patterns.
I need to add Redis?
First understand whether caching solves an actual problem.
I need Kafka?
First understand whether something should be asynchronous.
I need a read replica?
First understand the read volume and database bottleneck.
I need strong consistency?
First understand what happens if users temporarily see different data.
That’s probably the biggest shift in how I want to approach system design.
Not:
“What architecture do I know for this problem?”
But:
“What questions do I need to answer so that the architecture becomes obvious?”
And honestly, that’s probably what I should be practicing when I practice system design.
Not drawing more diagrams.
Getting better at asking the next useful question
Top comments (0)