Definition
- System design interview : a structured conversation where the interviewer gives an open-ended problem and evaluates how you decompose it into a scalable, reliable architecture.
- Step 1 : Clarify requirements : ask questions to separate functional requirements (what the system does) from non-functional requirements (how well it does it) and pin down scale.
- Step 2 : High-level design : draw the major components as boxes and label the connections between them with protocols and data flows.
- Step 3 : Deep dive : zoom into the two or three components that carry the most risk, complexity, or load and explain how they work internally.
- Step 4 : Trade-offs and scaling : openly discuss the compromises you made, name the bottlenecks, and explain how the system grows under increasing load.
Imagine Rajesh walks into National Bank of India and says "I want to build a money transfer service." A good banker does not immediately start digging the vault. First the banker asks questions : how many customers, how much money per day, do you need instant transfer or overnight settlement. Then the banker sketches the departments on a whiteboard : the front desk, the ledger room, the vault, the audit office. Then the banker zooms into the one department that is hardest, like the ledger room that must never lose a rupee. Finally the banker explains the compromises : "if you want instant transfer, it costs more; if overnight is fine, it is cheaper and safer." That is exactly the four-step framework.
Understanding the Concepts
What are the 4 steps of a system design interview?
The four-step framework is the universally accepted structure for answering a system design question in an interview. It keeps you organized, shows the interviewer you think before you code, and ensures you cover requirements, architecture, depth, and trade-offs in the time available. Skipping any step, especially the first, is the most common reason candidates fail.
Why does clarifying requirements come first?
The first step, clarifying requirements, is the one candidates most often skip and the one interviewers weight most heavily. You separate functional requirements, which describe what the system does, from non-functional requirements, which describe how well it does it. For a banking transfer system a functional requirement is "a customer can transfer money from one account to another," while a non-functional requirement is "the balance must never be wrong even if two servers crash." You also pin down scale numbers early so every later decision is grounded in reality.
package com.bank.design;
public class RequirementEstimation {
public static void main(String[] args) {
// Back-of-envelope spoken during step 1
long dailyTransfers = 10_000_000L; // 10 million transfers/day
long secondsPerDay = 86_400L;
long averageTps = dailyTransfers / secondsPerDay; // about 115 TPS
long peakTps = averageTps * 5; // peak ~575 TPS
System.out.println("Average TPS: " + averageTps);
System.out.println("Peak TPS: " + peakTps);
}
}
Average TPS: 115
Peak TPS: 575
How do you draw the high-level design?
The second step is where you draw boxes for the client, an API gateway, the transfer service, the account database, a message queue for notifications, and a cache for balances. You connect them with labelled arrows showing REST calls, database reads and writes, and asynchronous events. You are not writing code here; you are showing you can see the whole city before zooming into one building.
package com.bank.design;
import java.util.List;
public class HighLevelComponents {
// The major components you would draw as boxes
static final List<String> COMPONENTS = List.of(
"Mobile Client",
"API Gateway (auth, rate-limit)",
"Transfer Service",
"Account Database (PostgreSQL, primary)",
"Redis Cache (balance reads)",
"Kafka (transfer.completed events)",
"Notification Service"
);
public static void main(String[] args) {
System.out.println("=== High-Level Components ===");
COMPONENTS.forEach(c -> System.out.println(" [" + c + "]"));
}
}
What do you deep-dive into?
The third step is where you pick the component with the most risk. In a banking transfer that is almost always the part that debits one account and credits another atomically. You would explain how you use a database transaction so that either both the debit and credit happen or neither happens, and how you use an idempotency key so a retried request does not transfer twice.
package com.bank.service;
import java.math.BigDecimal;
import org.springframework.transaction.annotation.Transactional;
public class TransferService {
@Transactional
public void transfer(String fromAccount, String toAccount,
BigDecimal amount, String idempotencyKey) {
if (idempotencyStore.exists(idempotencyKey)) return; // safe retry
accountRepo.debit(fromAccount, amount); // both in one transaction
accountRepo.credit(toAccount, amount); // either both or neither
idempotencyStore.record(idempotencyKey);
}
}
How do you discuss trade-offs?
The fourth step is where senior candidates separate themselves. You explicitly say "I chose strong consistency over availability here because it is money; a social feed could choose the opposite." You name bottlenecks like the single primary database and explain how sharding by account id relieves it.
package com.bank.design;
public class TradeOffMatrix {
public static void main(String[] args) {
System.out.println("Trade-off | Banking choice | Social feed choice");
System.out.println("-----------------|--------------------|--------------------");
System.out.println("Consistency | Strong (CP) | Eventual (AP)");
System.out.println("Write latency | Higher (sync rep.) | Lower (async rep.)");
System.out.println("Scaling writes | Shard by account | Shard by user");
System.out.println("Cache policy | Write-through | Cache-aside + TTL");
}
}
How should you time-box each step?
A senior nuance is to time-box yourself. Spend about five minutes clarifying, ten minutes on high-level design, fifteen on deep dive, and the rest on trade-offs, so you never run out of time before reaching the discussion the interviewer cares about most. Many candidates spend too long drawing boxes and never reach the depth that demonstrates real expertise.
package com.bank.design;
public class TimeBoxGuide {
public static void main(String[] args) {
int total = 45; // typical interview length in minutes
System.out.println("Step 1 : Clarify requirements ~5 min (" + (5 * 100 / total) + "%)");
System.out.println("Step 2 : High-level design ~10 min (" + (10 * 100 / total) + "%)");
System.out.println("Step 3 : Deep dive ~15 min (" + (15 * 100 / total) + "%)");
System.out.println("Step 4 : Trade-offs & scaling ~15 min (" + (15 * 100 / total) + "%)");
}
}
What are common mistakes candidates make?
The most common mistake is jumping straight to a solution without asking a single question. The interviewer deliberately leaves the problem vague to see whether you clarify. Another mistake is going too deep on one component and never returning to the big picture. A third mistake is naming technologies without explaining why : saying "I will use Kafka" without explaining that the transfer notification is asynchronous and must survive a service restart.
package com.bank.design;
public class CommonMistakes {
// WRONG: jump to solution without clarifying
// "I will use Kafka, Redis, PostgreSQL, and Kubernetes."
// Interviewer thinks: "Why? For what scale? What requirements?"
// CORRECT: clarify first
// "Before I design, let me ask: how many transfers per day?
// Is it instant or overnight settlement?
// What is the availability target?"
}
What should a senior engineer emphasize?
A senior engineer names the constraints that drive decisions rather than just naming tools. They say "I chose PostgreSQL for the ledger because I need ACID transactions for money" rather than "I like PostgreSQL." They explicitly state which non-functional requirement drove each architectural choice, and they acknowledge what they sacrificed. They also connect the deep dive back to the requirements : "this atomic transfer is what guarantees our non-functional requirement that the balance is never wrong."
package com.bank.design;
public class SeniorSignals {
// Senior: connect requirement to decision
// "Our NFR says balance must never be wrong."
// "That means strong consistency on the debit-credit path."
// "That means I use a single-leader DB transaction, not eventual consistency."
// "The cost is write latency and a single-primary bottleneck."
// "I relieve the bottleneck by sharding by account id."
}
What are common interview follow-up questions?
Interviewers commonly follow up with: "What if your primary database goes down?" (failover and replication), "What if traffic doubles overnight?" (horizontal scaling and auto-scaling), "How do you know the system is healthy?" (monitoring, alerting, SLOs), and "What would you change if this were a social feed instead of a bank?" (relax consistency to AP, use eventual consistency, prioritize availability).
Visual Overview
Step 1: Clarify Step 2: High-Level Design
┌─────────────────┐ ┌──────────────────────────────────────┐
│ Functional Reqs │ │ [Client] │
│ - transfer money │ │ │ REST / HTTPS │
│ - view balance │ │ ▼ │
│ │ │ [API Gateway] │
│ Non-Functional │ │ │ auth, rate-limit │
│ - p99 < 200ms │ │ ▼ │
│ - 4 nines avail │ │ [Transfer Service] │
│ - strong consist │ │ │ │ │
│ │ │ ▼ ▼ │
│ Scale │ │ [PostgreSQL] [Kafka] │
│ - 10M txns/day │ │ (ledger) (events) │
│ - ~575 peak TPS │ │ │ │
└─────────────────┘ │ ▼ │
│ [Notification Service] │
└──────────────────────────────────────┘
Step 3: Deep Dive Step 4: Trade-offs
┌─────────────────────┐ ┌───────────────────────────────┐
│ Atomic Transfer │ │ Consistency vs Availability │
│ │ │ - Money: CP (strong) │
│ BEGIN TRANSACTION │ │ - Feed: AP (eventual) │
│ debit(from, amt) │ │ │
│ credit(to, amt) │ │ Bottleneck: single primary DB │
│ record(idem_key) │ │ - Relieve with sharding │
│ COMMIT │ │ - Read replicas for reports │
└─────────────────────┘ └───────────────────────────────┘
Key Takeaway
A system design interview follows a disciplined four-step framework, and the single biggest mistake candidates make is skipping straight to a solution. The first step is to clarify requirements and scope. I always separate functional requirements, which describe what the system must do, from non-functional requirements like latency, availability, and consistency, and I pin down the scale with back-of-envelope estimation. For a banking transfer system I would ask how many transfers per day, convert that to transactions per second, and confirm whether the money must move instantly or can settle overnight.
The second step is high-level design, where I draw the major components as boxes and label the connections between them. For a transfer system that means a client, an API gateway, the transfer service, the primary database as the source of truth, a cache for balances, and a message queue for asynchronous notifications. This diagram becomes the shared contract that the whole team can agree on before anyone writes a line of code.
The third step is to deep-dive into the two or three components that carry the most risk. In a banking system that is almost always the atomic debit and credit, because if the server crashes in the middle, money could vanish or be created from nothing. I explain how I wrap the debit and credit in a single database transaction so both happen or neither happens, and how I use an idempotency key so a retried request never transfers twice.
The fourth step is to discuss trade-offs, bottlenecks, and scaling, and this is where senior engineers stand out. I explicitly state that I chose strong consistency over availability because it is money, whereas a social media feed could choose the opposite. I name the single primary database as the bottleneck and explain how sharding by account id relieves it, and I add read replicas to serve reports without slowing down writes. What I have learned from real projects is that disciplined clarification at the start and honest trade-off discussion at the end are what separate a credible design from a pile of fashionable buzzwords.
Remember one sentence: clarify requirements first, sketch boxes second, deep-dive the riskiest component third, and discuss trade-offs last : never jump straight to a solution.
---