System design interviews evaluate whether you can turn ambiguous requirements into an explicit architecture, quantify the decisions that matter, and defend trade-offs under failure. The exact duration and expected depth vary by company and level, so use a framework as a navigation aid rather than a memorized script.
Table of Contents
- Framework Questions
- Requirements Gathering Questions
- Scale Estimation Questions
- High-Level Design Questions
- Deep Dive Questions
- Common Patterns Questions
- Classic Problems Questions
- Follow-Up Questions
- Quick Reference
Framework Questions
These questions test your ability to approach system design systematically.
How should you structure a system design interview?
System design interviews are collaborative technical discussions. Your goal is to make assumptions and trade-offs visible, connect requirements to components, and adapt when the interviewer changes the scenario. The following six-step framework is a useful default for a 45-minute prompt, not a proven universal rubric.
The framework breaks down as follows; adjust the time after confirming the format:
- Clarify requirements — scope the core user flows, correctness, latency, availability, durability, privacy, and explicit exclusions.
- Estimate decision-driving scale — QPS distributions, concurrency, payload/retention, bandwidth, hot-key or tenant skew, and growth.
- Define contracts and ownership — APIs/events, idempotency, schema, source of truth, partition keys, and consistency boundaries.
- Draw the end-to-end design — the smallest path that handles one write and one read correctly.
- Deep dive — analyze the two or three risks that dominate this problem, including alternatives and why you rejected them.
- Test and operate — overload, dependency failure, retries, recovery, migrations, observability, security, cost, and rollout.
Requirements Gathering Questions
These questions test whether you understand requirements before designing.
What questions should you ask before starting a design?
Ask enough questions to resolve decisions that would materially change the design, then state reasonable assumptions and move forward. Spending a fixed five minutes on generic questions can be as unhelpful as designing immediately.
"Before I dive in, I'd like to understand the requirements better..."
Functional requirements - What should the system do?
- Core features only (don't over-scope)
- User actions and flows
- Input/output expectations
Non-functional requirements - How well should it do it?
- Scale: How many users? Requests per second?
- Performance: Acceptable latency?
- Availability: What uptime is required?
- Consistency: Can data be eventually consistent or must it be strong?
Constraints:
- Are we building from scratch or integrating with existing systems?
- Any technology preferences or restrictions?
- Budget or team size considerations?
Scale Estimation Questions
These questions test your ability to think about real-world numbers.
How do you estimate scale for a social feed?
Back-of-envelope calculations show you think about real-world constraints. These estimates inform your architecture decisions—a read-heavy system needs different optimization than a write-heavy one.
Treat inputs as interview assumptions, not current facts about a named product. Include peak factors and explain which estimate changes a design choice.
Example scenario:
Users: 500M monthly active users (assumption)
Daily active: 200M (40%)
Tweets per day: 500M (avg 2.5 per active user)
Reads per day: 200M users × 100 tweets viewed = 20B reads
Tweets per second: 500M / 86400 ≈ 6000 TPS (write)
Reads per second: 20B / 86400 ≈ 230,000 QPS (read)
Storage per tweet: 280 chars + metadata ≈ 500 bytes
Daily storage: 500M × 500 bytes = 250GB
Yearly payload: 250GB × 365 = ~91TB
These are daily averages. Apply an explicit peak multiplier and include IDs, indexes, reactions, replication, backups, retention, compression, and media separately. The scenario is read-heavy by operation count, but feed generation, fan-out, cache memory, and hot-account skew may dominate more than raw read QPS.
High-Level Design Questions
These questions test your ability to design system architecture.
How do you design a high-level architecture for a social feed?
Start by drawing the main components and explaining the data flow. Walk the interviewer through how a request travels through your system, explaining each component's purpose.
flowchart TB
CDN["CDN<br/>(static assets)"]
CLIENT["Client"]
LB["Load Balancer"]
API["API Servers"]
CACHE["Cache<br/>(Redis)"]
TWEET_SVC["Tweet Service"]
TIMELINE_SVC["Timeline Svc"]
TWEET_DB["Tweet DB<br/>(Sharded)"]
TIMELINE_CACHE["Timeline Cache<br/>(Redis)"]
CDN --> CLIENT
CLIENT --> LB
LB --> API
API --> CACHE
API --> TWEET_SVC
API --> TIMELINE_SVC
TWEET_SVC --> TWEET_DB
TIMELINE_SVC --> TIMELINE_CACHEFigure 1. One candidate architecture for the stated feed assumptions, not a claim about a real company's internals. Every cache, queue, shard, and precomputed timeline must be justified by the estimated workload and freshness contract.
Walk through the flow:
"When a user posts a tweet: request hits the load balancer, goes to an API server, which writes to the Tweet database. Then we need to update timelines - this is where it gets interesting.
For reading timelines, we want to avoid expensive database queries, so we pre-compute timelines and store them in Redis. When you open the app, we just read from cache.
The challenge is: when should we update these cached timelines?"
How do you define APIs and data models?
Interfaces and data ownership should be defined early enough to expose idempotency, pagination, consistency, and access patterns. REST is one option; normalized relational tables, denormalized read models, logs, and document/key-value layouts are choices driven by invariants and queries, not interview defaults.
API Design:
POST /tweets
body: { text, media_ids }
returns: { tweet_id, created_at }
GET /timeline
params: ?cursor=xxx&limit=20
returns: { tweets: [...], next_cursor }
GET /users/{id}/tweets
returns: { tweets: [...] }
POST /follow/{user_id}
DELETE /follow/{user_id}
Data Model:
User
- id (PK)
- username
- email
- created_at
Tweet
- id (PK)
- user_id (FK)
- text
- created_at
- media_urls
Follow
- follower_id (PK, FK)
- followee_id (PK, FK)
- created_at
Deep Dive Questions
These questions test your ability to go deep on specific components.
What is the difference between fan-out on write and fan-out on read?
This is the classic Twitter timeline design problem. The choice between push and pull models has significant implications for write latency, read latency, and storage requirements.
Fan-out on Write (Push model):
When user posts tweet:
1. Write tweet to DB
2. Get all followers (could be millions)
3. Push tweet to each follower's timeline cache
Pros: Fast reads - timeline is pre-computed
Cons: Slow writes for users with many followers (celebrities)
High storage - tweet duplicated N times
Fan-out on Read (Pull model):
When user reads timeline:
1. Get list of who they follow
2. Fetch recent tweets from each
3. Merge and sort
Pros: Fast writes - just store the tweet once
Cons: Slow reads - must query multiple users
High compute at read time
Illustrative hybrid approach:
- Regular users: Fan-out on write
- High-fan-out accounts: defer or batch fan-out according to measured cost
When building timeline:
1. Read pre-computed timeline (regular users' tweets)
2. Merge with celebrity tweets fetched on-demand
How do you shard a database?
Do not infer that a single database fails solely from a daily row count. Estimate peak writes, row/index bytes, query shapes, retention, maintenance, and tested headroom. If partitioning is required, choosing ownership and migration protocols is critical.
"Shard-key options depend on access patterns:
- User ID: co-locates an author's posts, but a hot author can create skew and home timelines still span authors.
- Post ID or hash slot: spreads point lookups/writes, while author queries need a separate index/read model.
- Time range: helps retention and time scans but creates a hot current range unless further partitioned.
- Directory/range mapping: supports controlled movement at the cost of a routing control plane.
I would choose from the dominant queries and quantify skew, cross-shard work, secondary-index ownership, uniqueness, and online resharding."
How do you address bottlenecks and ensure reliability?
The final step is identifying potential bottlenecks and explaining how to address them. This shows you think about production-ready systems.
Scaling:
- Horizontal scaling of API servers behind load balancer
- Database read replicas for read-heavy workload
- Sharding for write scaling
Reliability:
- Define RPO/RTO, failure domains, backup/restore, and tested failover rather than merely saying “multiple data centers.”
- Choose synchronous or asynchronous replication from durability, latency, and stale-read requirements.
- Use deadlines, bounded retries with jitter, circuit breaking, idempotency, load shedding, and capacity headroom together.
Observability:
- Request latency percentiles (p50, p95, p99)
- Error and timeout rates by dependency and operation
- Saturation, queue depth/age, replication lag, retry amplification, and recovery progress
- Correctness/business signals such as lost/duplicate events, stale reads, and successful user flows
Common Patterns Questions
These questions test your knowledge of reusable system design patterns.
How does cache-aside pattern work?
Caching is essential for read-heavy systems. The cache-aside pattern (also called lazy loading) is the most common caching strategy, where the application manages the cache explicitly.
flowchart TB
REQ["Read Request"]
CHECK["Check Cache"]
HIT["Return Data"]
MISS["Query Database"]
UPDATE["Update Cache"]
RETURN["Return Data"]
REQ --> CHECK
CHECK -->|"Cache Hit"| HIT
CHECK -->|"Cache Miss"| MISS
MISS --> UPDATE
UPDATE --> RETURNFigure 2. The basic cache-aside read path. Production behavior also needs miss coalescing, negative caching, freshness/version rules, safe key dimensions, and origin protection. A flowchart does not close races between a refill and invalidation.
Cache-aside (Lazy Loading):
async function getUser(userId) {
const key = `tenant:${tenantId}:user:${userId}`;
const cached = await cache.get(key);
if (cached !== null) return cached;
return singleFlight(key, async () => {
const user = await db.findUser({ tenantId, userId });
await cache.set(key, user ?? NOT_FOUND, { ttl: ttlWithJitter() });
return user;
});
}Cache-aside invalidation after a committed write:
async function updateUser(userId, data) {
await db.transaction(async tx => {
const version = await tx.updateUser({ tenantId, userId, data });
await tx.outbox.add({ type: 'UserChanged', tenantId, userId, version });
});
}An outbox/CDC consumer invalidates or version-guards every cache layer. Even “commit DB, then delete cache” has a stale-repopulation race when an older read finishes after deletion. Use bounded TTL staleness, versions/generations, immutable versioned keys, or bypass caching according to the data contract. See the distributed cache design for the full race.
How does rate limiting work?
Rate limiting protects capacity and enforces a product policy; abuse prevention also needs authentication, anomaly detection, and cost controls. Define the identity, scope, distributed consistency, burst, time source, failure mode, and response headers before choosing an algorithm.
flowchart TB
REQ["Request"]
LIMITER["Rate Limiter<br/>(Token Bucket)"]
ALLOWED["Process Request"]
REJECTED["Return 429"]
REQ --> LIMITER
LIMITER -->|"Allowed"| ALLOWED
LIMITER -->|"Rejected"| REJECTEDFigure 3. A rate limiter in the request path. The token bucket decides per request whether to allow or reject; rejected requests return 429. Putting the limiter at the edge means abusive traffic is stopped before it consumes any backend capacity - the cheapest place to say no.
Token Bucket Algorithm:
class RateLimiter {
constructor(capacity, refillRate) {
this.capacity = capacity; // Max tokens
this.tokens = capacity; // Current tokens
this.refillRate = refillRate; // Tokens per second
this.lastRefill = Date.now();
}
allowRequest() {
this.refill();
if (this.tokens >= 1) {
this.tokens -= 1;
return true;
}
return false;
}
refill() {
const now = Date.now();
const elapsed = (now - this.lastRefill) / 1000;
this.tokens = Math.min(this.capacity, this.tokens + elapsed * this.refillRate);
this.lastRefill = now;
}
}When should you use message queues?
Queues or logs decouple production from processing in time, but they move pressure into a backlog rather than remove it. Specify durability acknowledgement, ordering scope, delivery semantics, retention, retry/dead-letter policy, idempotency, poison-message handling, lag SLOs, and what happens when the backlog exceeds recovery capacity.
flowchart LR
PRODUCER["Producer"] --> QUEUE["Message Queue<br/>(Kafka/SQS)"]
QUEUE --> CONSUMER["Consumer"]Figure 4. A queue can buffer a bounded burst and let consumers recover independently, provided the publish acknowledgement is durable enough and backlog growth is controlled. “Kafka” and “SQS” are not interchangeable labels: ordering, retention, redelivery, and consumer models differ.
Use cases:
- Decoupling services (post service → notification service)
- Handling traffic spikes (queue absorbs burst)
- Async processing (image resizing, email sending)
- Durable integration events via a transactional outbox
Event sourcing is a separate domain persistence model in which events are the source of truth; merely sending jobs through a queue is not event sourcing.
How does database replication work?
Database replication creates additional copies or replay streams. It may add read capacity and improve recovery, but the guarantees depend on synchronous/asynchronous acknowledgement, replica apply lag, read routing, conflict model, failover election, fencing, and persistence.
flowchart TB
WRITES["Writes"]
PRIMARY["Primary<br/>Database"]
R1["Replica 1"]
R2["Replica 2"]
READS["Reads"]
WRITES --> PRIMARY
PRIMARY -->|"Replication"| R1
PRIMARY -->|"Replication"| R2
R1 --> READS
R2 --> READSFigure 5. One primary-replica variant. Asynchronous replicas can return stale data and lose recent acknowledged writes on failover; synchronous acknowledgement increases latency and can reduce write availability. Promotion needs an external decision and fencing so the old primary cannot keep writing.
Classic Problems Questions
These questions test your ability to apply patterns to common interview problems.
How do you design a URL shortener?
URL shorteners are useful because a small API exposes ID allocation, abuse prevention, redirect semantics, cacheability, analytics, and data lifecycle. Treat the following scale as an assumption to validate.
Requirements:
- Shorten long URLs
- Redirect short URLs
- Custom short codes (optional)
- Analytics (optional)
Scale:
- 100M URLs created per month (scenario input)
- 10:1 read:write ratio
- 7 characters = 62^7 = 3.5 trillion combinations
Design:
POST /shorten
body: { long_url, custom_code? }
returns: { short_url }
GET /{short_code}
returns: 302 redirect to long_url
Key decisions:
- ID generation: Counter + base62 encode, or random generation with collision check
- Storage: choose SQL or a key-value store from uniqueness, transactions, secondary queries, operations, and measured scale
- Redirect: choose temporary versus permanent status and cache policy from mutability/product behavior
- Safety: allowlist URL schemes, prevent internal/admin destinations where applicable, scan abuse, rate-limit creation, and handle takedowns
- Caching: hot mappings only when invalidation/version rules and origin protection are defined
- Analytics: durable asynchronous events with consent, retention, privacy, and idempotent aggregation
How do you design a chat system?
Chat systems introduce long-lived gateways, multi-device routing, ordered durable history, approximate presence, offline sync, and idempotent delivery. WebSocket is a strong transport option, not the whole architecture.
Requirements:
- 1-on-1 and group messaging
- Online status
- Message history
- Push notifications
Key components:
- WebSocket servers for real-time communication
- Message queue for delivery guarantee
- User presence service (heartbeat-based)
- Push notification service (APNs/FCM)
Message flow:
flowchart TB
USER_A["User A sends message"]
WS["WebSocket Server"]
QUEUE["Message Queue (Kafka)"]
STORE["Message Store<br/>(Cassandra)"]
DELIVERY["Delivery Service"]
ONLINE["User B online<br/>→ WebSocket push"]
OFFLINE["User B offline<br/>→ Push notification"]
USER_A --> WS
WS --> STORE
STORE -->|committed delivery event| QUEUE
QUEUE --> DELIVERY
DELIVERY --> ONLINE
DELIVERY --> OFFLINEFigure 6. Acceptance follows an atomic message/idempotency/delivery-event commit, commonly via an outbox or log. Delivery is retryable and may duplicate; every device deduplicates and repairs sequence gaps from history. Push notification is a hint, not the source of truth.
How do you design a distributed rate limiter?
Distributed rate limiting is more complex than single-server rate limiting because you need to coordinate state across multiple servers. Redis provides atomic operations that make this tractable.
Requirements:
- Limit requests per user/IP
- Different limits for different APIs
- Distributed (multiple servers)
Algorithms:
- Token Bucket: Smooth rate limiting, allows burst
- Leaky Bucket: Fixed rate output
- Fixed Window: Simple but edge case at window boundaries
- Sliding log/counter: finer boundary behavior at higher state or approximation cost
Redis 8.8+ fixed-window example:
INCREX rate_limit:{policy}:{subject}:{window} \
BYINT 1 UBOUND 100 EX 60 ENXINCREX atomically increments, caps, and sets expiry only for a new window; an applied increment of zero means reject. On older Redis versions, use a tested Lua/function equivalent—not separate INCR and EXPIRE operations that can race or accidentally extend the window. Fixed windows still allow boundary bursts; token bucket or sliding-counter designs have different precision and state costs. Return 429 with useful retry metadata, protect Redis from cardinality attacks, and decide whether limiter failure fails open or closed per endpoint.
Follow-Up Questions
These questions test your ability to handle curveballs and edge cases.
How would you handle a celebrity posting a tweet?
High-fan-out accounts can make synchronous push work exceed the latency and queue budget. This follow-up tests whether you can adapt from workload measurements rather than a fixed follower threshold.
"I would commit the post once, fan out asynchronously for accounts whose predicted cost fits the budget, and use pull-on-read or change hints for unusually high fan-out. The policy uses measured follower activity, queue lag, freshness, and read amplification—not an invented 10K threshold. Reads merge sources with stable ordering, deduplication, pagination, and backpressure."
What happens if the database goes down?
This question tests your understanding of fault tolerance and disaster recovery. A production system needs to handle failures gracefully.
"First define the failure: process, node, zone, corruption, or operator error. Replication helps availability but asynchronous failover can lose acknowledged commits and stale replicas may violate read-your-writes. Promotion needs health evidence, quorum/authority, fencing, client rerouting, and a tested method to rebuild redundancy. Backups plus restore drills handle corruption and deletion.
I would accept writes into a queue during outage only if the queue is intentionally the durable system of record for that command, can validate and idempotently order it, has bounded backlog capacity, and the product accepts delayed visibility. A queue is not a drop-in write-ahead log for arbitrary database transactions. Otherwise fail writes clearly and let clients retry with idempotency keys."
How do you ensure consistency in a distributed system?
This question tests your understanding of CAP theorem and consistency models. The answer depends on the use case—different data has different consistency requirements.
"Start with the invariant and operation: a ledger posting, uniqueness constraint, feed counter, and presence indicator need different guarantees. Keep critical invariants inside one serializable transactional boundary when possible; distributed transactions can coordinate atomic commit but add blocking and failure costs. A Saga is a sequence of local commits with compensating actions and usually eventual consistency—it is not a synonym for strong consistency or rollback.
For derived counters, bounded staleness and reconciliation may be acceptable. State the bound, source of truth, monotonicity/idempotency, and repair path. CAP only constrains linearizability plus availability during a partition in its formal model; it does not make one permanent choice for an entire social application."
Quick Reference
Component Selection Guide
| Component | When to Use |
|---|---|
| Load Balancer | Multiple servers, high availability |
| CDN | Static assets, global users |
| Cache (Redis) | Read-heavy, acceptable staleness |
| Message Queue | Async processing, decoupling |
| Database Sharding | Single DB can't handle write load |
| Read Replicas | Single DB can't handle read load |
| Key-value/document store | Access patterns fit its keys/indexes and its consistency/operational model |
| Relational database | Invariants, transactions, relational queries, and mature operational fit |
Key Concepts Summary
| Concept | Remember |
|---|---|
| CAP Theorem | During a partition, linearizability and availability cannot both hold for every request |
| Horizontal scaling | Add nodes plus routing, partitioning/replication, movement, and failure handling |
| Vertical scaling | Add node resources; often simple and useful, still bounded |
| Fan-out on write | Pre-compute, fast reads, slow writes |
| Fan-out on read | Compute on demand, fast writes, slow reads |
Practice Questions
Test yourself before your interview:
1. Design a parking lot system. What are the key components and how do you handle multiple entry/exit points?
2. Design Instagram. How would you handle image storage and delivery at scale?
3. You're designing a notification system. How do you ensure notifications are delivered even if the user's device is offline?
4. Design a web crawler. How do you avoid crawling the same page twice?
Frequently Asked Questions
How do you approach a system design interview?
Use a flexible loop: clarify scope and success metrics, estimate only the dimensions that drive decisions, define interfaces and data ownership, draw the simplest end-to-end path, deepen the highest-risk components, then test failures and operations. Time boxes depend on the interview format; confirm priorities with the interviewer, state assumptions, compare alternatives, and revise the design as requirements change.
What is horizontal vs vertical scaling?
Vertical scaling adds CPU, memory, storage, or network capacity to a node and can be the simplest economical step. Horizontal scaling adds nodes and requires partitioning or replication, routing, rebalancing, coordination, and failure handling. Neither automatically provides availability and horizontal scale is not unlimited: shared databases, hot keys, metadata, network, consistency, and operational complexity remain bottlenecks. Most systems combine both.
What is a load balancer and why is it important?
A load balancer selects a healthy backend for a connection or request using policies such as weighted round-robin, least connections, latency-aware routing, or consistent hashing. It can improve availability and capacity only when health checks, draining, retry budgets, backend headroom, and failure domains are correct. It does not know the truly least-busy server unless that signal is measured, and retries can amplify overload. Layer 4 balances transports; Layer 7 can route using HTTP semantics.
What is database sharding?
Sharding partitions ownership across database shards using a key or routing directory. It can distribute storage and throughput but introduces cross-shard queries and transactions, secondary indexes, hot tenants, uniqueness, rebalancing, and failure-domain concerns. Avoid coupling hash(key) directly to the current shard count; use stable logical slots/ranges or a versioned directory and an online migration protocol. Choose the key from access patterns and skew, not only even row counts.
What is the CAP theorem?
CAP is not a menu where a system permanently chooses any two. Under the formal model, when messages are lost across a network partition, a replicated read/write service cannot provide both linearizable consistency and availability for every request to every non-failing node. A design chooses behavior per operation during that partition, while latency, durability, isolation, recovery, and normal-operation consistency remain separate dimensions. Product labels such as CP or AP are only shorthand after the exact guarantees are stated.
When should you use caching?
Cache when measured miss cost, reuse, and freshness tolerance justify the memory and invalidation complexity. Define keys, tenant and Vary dimensions, negative caching, TTL/stale bounds, stampede protection, eviction/admission, and origin capacity. Cache-aside DB-then-delete still has a stale-repopulation race; stricter data may need commit-driven invalidation plus versions, immutable versioned keys, or no cache. Every CDN, L1, and replica adds another invalidation path.
Sources
- Gilbert and Lynch: Perspectives on the CAP Theorem
- Redis 8.8+: atomic
INCREXwindow-counter pattern - IETF RFC 6585: 429 Too Many Requests
- PostgreSQL: streaming and synchronous replication
- PostgreSQL: failover and old-primary fencing
- Apache Kafka documentation: partitions, ordering, replication, and delivery semantics
- Google SRE: Handling Overload
Related Articles
- Complete Node.js Backend Developer Interview Guide - comprehensive preparation guide for backend interviews
- SQL JOINs Interview Guide - Master JOIN types with visual examples
- REST API Interview Guide - API design principles and best practices
- Node.js Advanced Interview Guide - Event loop, streams, and Node.js internals
- Complete DevOps Engineer Interview Guide - comprehensive preparation guide for DevOps interviews
- Docker Interview Guide - Containers, images, and production-ready Dockerfiles
- Kubernetes Interview Guide - Container orchestration, pods, and deployments
