A framework for system design interviews
System design interviews intimidate most engineers because they feel unstructured—you're staring at a blank whiteboard with vague requirements and no clear starting point. The truth is, I've been designing real production systems for years, and the same six-step approach I use for that work translates directly to interviews. This framework removes the chaos and gives you a reliable process to follow every single time.
Why frameworks matter in design
Before I outline the six steps, let me explain why a framework exists at all. When you're designing a system—whether it's a ride-hailing platform handling millions of requests, an e-commerce checkout flow, or a real-time analytics engine—you need a mental model to walk through systematically. Without one, you jump between concerns: "Should we use PostgreSQL or Cassandra?" before you know what scale you're operating at, or you obsess over API design before understanding the actual functional requirements.
I've seen talented engineers derail interviews by diving into implementation details (database indexing strategies, cache invalidation) when the interviewer actually wanted to hear about your reasoning for choosing a particular technology. A framework keeps you honest. It forces you to answer the hard question first: Are we even solving the right problem?
The six steps explained
Here's the framework I use for every design, interview or production:
- Clarify requirements and constraints
- Estimate scale: users, QPS, storage
- Define APIs and data model
- Draw the high-level design
- Deep dive into bottlenecks
- Discuss trade-offs and failure modes
I'll walk through each one with real scenarios from my work.
Step 1: Clarify requirements and constraints
This is where most interviews are won or lost. You get a vague prompt like "Design an Instagram clone" or "Design a notification system," and you have 30 seconds to resist the urge to start drawing boxes and lines.
Instead, ask questions. Functional requirements are obvious—what does the system actually do? For Instagram, that's uploading photos, following users, liking posts, viewing feeds. Non-functional requirements are where the real design happens: Is this read-heavy or write-heavy? Do we need real-time consistency, or is eventual consistency acceptable? What's the region? Do we have a mobile-first or web-first audience?
I worked on a travel booking platform where the initial brief was "Search for flights." Sounds straightforward. But when I asked clarifying questions, I learned that 90% of traffic came from mobile, search results needed to update within 3 seconds as prices changed dynamically, and we had regional data sovereignty requirements. Those constraints completely changed the architecture. Without clarification, I'd have designed a stateless REST API that would've struggled under the real load patterns.
Write down everything the interviewer tells you. Write down your assumptions too. Ask:
- Who are the primary users?
- Is this read-heavy, write-heavy, or balanced?
- Do we need real-time consistency?
- What are SLA expectations?
- Are there compliance or data residency requirements?
- What's the geographic distribution?
This takes 3-5 minutes, but it saves you from designing the wrong system.
Step 2: Estimate scale: users, QPS, storage
Numbers shape the design more than anything else. A system for 100 users with 10 requests per second looks nothing like a system for 100 million users at 100,000 requests per second. But many engineers skip this step or do it halfheartedly, then get surprised when their design falls apart at scale.
Let me give you a concrete example. For the e-commerce platform I built, we started with these estimates:
- Monthly active users: 5 million
- Daily active users: 500,000
- Peak QPS during sales: 50,000 requests per second
- Average order value: $50
- Data retention: 7 years of transaction history
From those numbers, I could calculate that we needed enough database throughput to handle 50k QPS writes during peak, enough cache to keep hot products in memory, and a data warehouse to analyze years of historical trends. A monolithic PostgreSQL setup would've been dead on arrival. We needed read replicas, sharding, caching layers, and a completely different data model for analytics.
Here's how I break down the estimation:
- Start with total users. If you don't know, make a reasonable assumption and state it.
- Estimate daily active users as a percentage of total.
- Estimate requests per second during peak. (Total DAU * requests per user per day / 86400 seconds, then multiply by 3-5x for peak.)
- Estimate storage. (Data per request * QPS * seconds in a year, roughly.)
- Estimate bandwidth. (Data size per request * QPS in bits per second.)
Let's work through a notification system example. Say we have 10 million users, 20% DAU (2 million active daily), and each user checks notifications 5 times per day on average. That's 10 million requests per day, or about 115 QPS on average. Peak might be 500-1000 QPS. Each notification is roughly 1KB of data. That's roughly 10GB per day of new notifications, or 3.6TB per year. Totally reasonable to keep in a standard PostgreSQL setup if reads are cached aggressively. But if we had 100 million users, suddenly we're at 360TB per year, and the architecture completely changes.
Always walk through these calculations out loud. Interviewers want to see your reasoning, not just the final number.
Step 3: Define APIs and data model
Now that you understand what you're building and at what scale, define the contract. What does the API look like? What tables or documents do you need?
For a social feed API, I'd define something like:
// Get user feed
GET /api/v1/feed?limit=20&cursor=abc123
Response: {
posts: [
{
id: "post_123",
authorId: "user_456",
content: "Hello world",
createdAt: "2025-01-15T10:30:00Z",
likeCount: 42,
commentCount: 8
}
],
nextCursor: "abc124"
}
// Create post
POST /api/v1/posts
{
content: "Hello world",
media: [{ url: "s3://...", type: "image" }]
}
And the data model might look like:
| Table | Purpose | Key Design Decision |
|---|---|---|
users |
User profiles | Denormalized for read performance |
posts |
Posted content | Sharded by user_id for write scalability |
feed_events |
Feed timeline events | Time-series, partitioned by date |
likes |
Post interactions | Denormalized into posts table for reads |
The key here is thinking about access patterns. How do you read? How do you write? For feeds, reads are hot (thousands of users pulling their feed constantly), so you optimize for read latency. Likes are written constantly but read less frequently, so you might denormalize the count into the posts table to avoid joins.
Step 4: Draw the high-level design
Now sketch the architecture. This is where you draw boxes and arrows. I'm talking about client layer, API layer, caching layer, database layer, message queues for async processing, maybe a search index, etc.
For the e-commerce platform, the high-level design looked roughly like:
Client → Load Balancer → API Servers (NestJS) → Redis Cache → PostgreSQL (read replicas)
Async Pipeline: Kafka → Workers → Data Warehouse (S3 + Redshift)
Search: Elasticsearch for product search
Don't get lost in details here. The point is to show you understand the major layers and how data flows through them. Explain your reasoning: Why Kafka instead of synchronous processing? Because order confirmations can't block checkout. Why Redis? Because product catalog reads are 100x more frequent than writes. Why read replicas? Because we can't handle 50k QPS on a single database node.
Step 5: Deep dive into bottlenecks
Now the interviewer asks, "What's going to break first?" This is where you show deep thinking. You've identified scale (50k QPS), so which component fails first?
- Database write throughput? Yes—a single PostgreSQL primary can handle maybe 10-20k QPS depending on hardware.
- Network bandwidth? At 50k QPS with 1KB payload, that's 50MB/s of outbound traffic. Not trivial, but manageable with modern infrastructure.
- Cache hit rate? If we're not caching 99%+ of reads, we're hitting the database too often.
For each bottleneck, propose a solution. Database writes? Shard by user_id. Cache misses? Pre-warm cache with popular products. And here's the key: you must verify your solution doesn't create a new bottleneck. Sharding introduces operational complexity and makes joins harder. Pre-warming takes resources. These are trade-offs.
Step 6: Discuss trade-offs and failure modes
This is where I separate confident engineers from the rest. The best designers don't defend their choices; they explain what they gave up and why it's worth it.
In the e-commerce platform, we chose eventual consistency for inventory counts. Why? Because a customer seeing a product available when it's actually sold out (for 100 milliseconds) is acceptable, but having a checkout button blocked while we wait for perfect consistency across 50 servers is not. We decided the UX cost of blocking checkout was higher than the cost of rare overselling. That's a trade-off.
We also chose PostgreSQL read replicas over a NoSQL database like Cassandra. Why? Because we needed ACID transactions for orders, and the read scalability of replicas was sufficient. Cassandra would've given us better write scalability but sacrificed transaction guarantees. Given that money moves through this system, that trade-off didn't make sense.
Talk about failure modes too. What happens if the cache goes down? (Requests hit the database; latency spikes, but the system stays up.) What if a worker crashes? (Messages stay in Kafka; they get reprocessed.) What if an entire region fails? (We have multi-region read replicas, so failover is automatic.)
Requirements first
I want to emphasize this again because I see it go wrong constantly: most bad designs answer the wrong question.
Two years ago, I was designing a ride-hailing matching system. The initial instinct was "We need a super-fast database like Redis to match drivers to riders instantly." We built this beautiful sorted-set structure in Redis, optimized the matching algorithm to micro-millisecond latency, and patted ourselves on the back.
Then we realized the real requirement was that the system needed to be fair—drivers waiting longest should get matched first. Instant matching doesn't matter if it's not fair; drivers revolt. We'd answered the speed question (solved: sub-100ms) but ignored the fairness question (broken: unfair distribution). We had to redesign around audit logs and fair-queuing, which introduced latency. The initial optimization was wasted effort.
So when you're clarifying requirements, really push on the non-functional ones. Not just performance, but fairness, compliance, observability, and operational burden. A system that requires on-call engineers to babysit it every weekend is a bad design, no matter how fast it is.
Numbers shape the design
I keep hammering this because it's foundational. Design decisions that make sense at 100 QPS are catastrophic at 100,000 QPS.
Let me give you a concrete example. For a travel booking platform, we initially pushed all booking confirmations synchronously—the API would wait for the database write, then return success to the client. This worked fine during development (10-20 QPS). At scale (during peak booking season, 5,000 QPS), latency ballooned because the database couldn't keep up with write throughput. We refactored to push confirmations into a Kafka queue, return success immediately, and process asynchronously. Same business logic, completely different architecture, driven purely by scale.
The estimation exercise forces you to know where the stress points are before you design. If you skip it, you're guessing. And guesses fail at scale.
Trade-offs are the answer
There is no perfect design. Not in interviews, not in production. Every choice has a cost.
Use caching? Great, you've solved read latency, but now you have to manage cache invalidation (which is famously one of the hardest problems in computer science). Use eventual consistency? Great, you've scaled writes, but now you have to handle stale reads and explain why a user's follow list didn't update for 3 seconds. Shard your database? Great, you've scaled horizontally, but now you can't do joins across shards and you have to handle resharding when you run out of capacity.
The best interview answer acknowledges these trade-offs explicitly. Say something like: "We're using eventual consistency here instead of strong consistency. That means a user might see stale data for a few seconds after they follow someone. That's acceptable for our use case because fresh followers aren't critical, but if we were designing a financial system, we'd choose differently."
This shows maturity. You're not just reciting a standard architecture; you're reasoning about what matters for this specific problem.
Common mistakes
I see the same patterns derail interviews repeatedly:
- Skipping requirements: You jump to architecture without understanding what "Instagram clone" actually means. Does it need to support 1 million users or 1 billion? Do we care about real-time feed updates? Ask first, design second.
- Wrong scale estimates: You assume 100,000 QPS without justification, or you estimate way too conservatively and design an over-engineered system. Show your math. 5M users × 20% DAU × 10 requests per day / 86400 seconds × 3 (peak multiplier) = X QPS. Reasonable?
- No acknowledgment of trade-offs: You propose a design and act like it's objectively correct. Every design has costs. Name them.
- Getting lost in implementation: You start talking about specific database tuning parameters or optimizing a sorting algorithm when you haven't justified why you chose that database in the first place. Stay at the right level of abstraction.
- Defensive about feedback: The interviewer challenges your choice. Instead of explaining your reasoning, you double down or shut down. Remember, they're testing your thinking, not looking for one right answer. Engage with the question.
Key takeaways
- Follow a framework: clarify requirements, estimate scale, define APIs, draw high-level design, deep dive into bottlenecks, discuss trade-offs. It removes chaos and keeps you focused.
- Functional requirements matter, but non-functional requirements (scale, consistency, latency, fairness) shape the actual design. Ask about both.
- Do the math. 10 requests per second and 10,000 requests per second lead to completely different systems. Estimation forces you to know the scale before you design.
- Talk through your trade-offs. Every technology choice has costs. The best designers name them explicitly instead of pretending their design is optimal.
- Stay at the right abstraction level. During high-level design, don't obsess over database indexes. During deep dives, go tactical. Meet the question where it is.
- Clarify assumptions constantly. If you're unsure about user distribution, region, or consistency requirements, ask. Wrong assumptions break the entire design.






Comments
Loading comments…