System Design Interview Questions with Answers
2025-02-22 · 4 min read
TL;DR — System design interviews test how you think about scale, trade-offs, and components. Start by clarifying requirements, then draw a high-level design. Here are common questions with structured answers.
1. Design a URL Shortener (e.g. bit.ly)
Clarify: 10M URLs/sec? 1B URLs stored? Short links expire? Custom slugs?
High-level design:
[Client] → [Load Balancer] → [App Servers] → [Database]
↓
[Cache (Redis)]
Key decisions:
- Short code — Base62 encode a random ID or auto-increment. 6 chars ≈ 56B combinations.
- Storage — DB:
(short_code, long_url, created_at). Index on short_code. - Cache — Redis:
short_code → long_url. Read-through cache. Hot URLs stay in cache. - Redirect — 302 (temporary) so analytics work; 301 (permanent) for SEO.
Code: Generate short code
function generateShortCode(): string {
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
let code = "";
for (let i = 0; i < 6; i++) {
code += chars[Math.floor(Math.random() * chars.length)];
}
return code;
}
async function createShortUrl(longUrl: string): Promise<string> {
const code = generateShortCode();
await db.urls.insert({ code, longUrl, createdAt: new Date() });
return `https://short.com/${code}`;
}
2. Design a Rate Limiter
Clarify: Per user or per IP? Sliding window or fixed? Distributed?
Approaches:
- Fixed window — Count requests per minute. Simple but allows burst at edge.
- Sliding window — Use Redis sorted set. Remove old entries; count in window.
- Token bucket — Refill tokens at rate. Allow burst up to bucket size.
Code: Sliding window with Redis
async function isAllowed(userId: string, limit: number, windowSec: number): Promise<boolean> {
const key = `ratelimit:${userId}`;
const now = Date.now();
const windowStart = now - windowSec * 1000;
await redis.zremrangebyscore(key, 0, windowStart);
const count = await redis.zcard(key);
if (count >= limit) return false;
await redis.zadd(key, now, `${now}-${Math.random()}`);
await redis.expire(key, windowSec);
return true;
}
3. Design a News Feed (e.g. Twitter timeline)
Clarify: 300M users? Read-heavy or write-heavy? Real-time or eventual?
Approaches:
- Pull (fan-out on read) — On read, fetch posts from followed users. Simple but slow for users with many followees.
- Push (fan-out on write) — On post, push to all followers' feeds. Fast read, slow write for celebrities.
- Hybrid — Push for normal users; pull for celebrities (e.g. >10K followers).
Storage:
- Posts —
(post_id, user_id, content, created_at) - Feed — Redis sorted set per user:
feed:user123→{post_id: score (timestamp)} - Timeline —
ZREVRANGE feed:user123 0 49for latest 50 posts.
4. Design a Distributed Cache
Clarify: Cache size? Eviction policy? Consistency?
Key concepts:
- Consistent hashing — Add/remove nodes without remapping all keys.
- Replication — Each key on N nodes for fault tolerance.
- Eviction — LRU (Least Recently Used). Redis uses approximate LRU.
Consistency:
- Cache-aside — App reads from cache; on miss, read DB and populate cache. On write, invalidate cache.
- Write-through — Write to cache and DB together. Cache is source of truth for reads.
5. Design a Chat System (e.g. WhatsApp)
Clarify: 1:1 or group? Real-time? Message persistence?
Components:
- WebSocket/HTTP long-polling — Real-time delivery.
- Message queue — Persist messages; fan-out to online recipients.
- Storage — Messages in DB. Index by (conversation_id, timestamp).
Flow:
- User sends message → API stores in DB and pushes to queue.
- Queue consumer fans out to online users in the conversation.
- WebSocket server pushes to connected clients.
6. How would you scale a database?
Options:
- Read replicas — Replicate writes to multiple read replicas. Scale reads.
- Sharding — Partition data by key (e.g. user_id). Each shard is a separate DB.
- CQRS — Separate read and write models. Write to one store; read from optimized read store.
Sharding example:
function getShard(userId: string): number {
const hash = hashCode(userId);
return Math.abs(hash) % 4; // 4 shards
}
const shard = getShard(userId);
const db = shards[shard];
const user = await db.users.findById(userId);
Summary
| Question | Key components |
|---|---|
| URL shortener | LB, app servers, DB, cache, Base62 encoding |
| Rate limiter | Redis, sliding window or token bucket |
| News feed | Fan-out on read vs write, hybrid for celebrities |
| Distributed cache | Consistent hashing, replication, eviction |
| Chat | WebSocket, message queue, persistence |
| DB scaling | Read replicas, sharding, CQRS |
For more on system design, see System Design Beginner Guide.