System Design Interview Guide: Complete 2026 Roadmap
From Back-of-the-Envelope Math to Distributed Trade-Offs: A Staff-Level Blueprint for HLD Rounds

What You Must Master to Clear This Track
- Always anchor your design in back-of-the-envelope math (QPS, storage, bandwidth) before drawing a single box.
- Treat CAP theorem as a spectrum of trade-offs, not a checkbox: articulate exactly what you sacrifice and why for each component.
- Master the 4-step framework: Scope -> Estimate & High-Level Design -> Component Deep-Dive -> Bottlenecks & Resilience.
- Know sharding, replication, caching, and message queues well enough to justify a specific configuration, not just name-drop them.
- Practice narrating 2-3 classic systems end-to-end (URL shortener, chat app, news feed) until the trade-off discussion becomes second nature.
Step-by-Step Study Plan
Follow this sequential roadmap designed to take you from core foundations to advanced architecture and mock interviews.
Scalability Fundamentals & CAP-Driven Thinking
Back-of-the-envelope estimation, vertical vs horizontal scaling, DNS/TCP/HTTP fundamentals, CAP theorem, PACELC, and consistency models.
- •Memorize the core latency numbers (memory vs SSD vs disk vs cross-region network) and practice deriving QPS/storage from DAU figures.
- •Explain CAP theorem trade-offs for at least 3 real databases (DynamoDB, Cassandra, Spanner, PostgreSQL).
- •Understand quorum-based consistency (W + R > N) and read-your-writes guarantees.
- •Time yourself doing a 5-minute capacity estimation for a hypothetical 50M DAU service every day.
- •Draw the CAP triangle from memory and place 5 well-known databases on it correctly.
Distributed Data & Asynchronous Processing
Database sharding strategies, leader-follower/leaderless replication, caching patterns, consistent hashing, message queues, and rate limiter algorithms.
- •Implement consistent hashing with virtual nodes to explain minimal data movement on node churn.
- •Compare cache-aside, write-through, and write-back, and diagnose cache stampede scenarios.
- •Implement a token-bucket rate limiter and explain distributed enforcement across multiple app servers.
- •Whiteboard a sharded, replicated database topology and narrate a write path and a read path separately.
- •List 3 production incidents caching or queueing could have prevented, and how.
The 4-Step Framework Under Time Pressure
Full end-to-end mock designs for a URL shortener, a WhatsApp-style chat system, and a news feed, plus bottleneck and resilience discussions.
- •Complete at least 8 full 45-minute mock HLD sessions covering distinct system archetypes.
- •Practice articulating single points of failure (SPOF), failover, and multi-region strategy for every design.
- •Record yourself and review pacing: 5 min scope, 10 min estimation/HLD, 20 min deep-dive, 10 min bottlenecks.
- •Never let a design go past 10 minutes without drawing a labeled box diagram.
- •Always end with 2-3 proactive bottleneck callouts before the interviewer asks.
1. Scalability Fundamentals & Back-of-the-Envelope Estimation
Every credible system design answer starts with numbers. Interviewers use your estimation instincts as a proxy for real-world engineering judgment long before any architecture diagram appears.
Vertical scaling (bigger machines) is simple but hits hard physical ceilings and creates a single point of failure; horizontal scaling (more machines) requires stateless services, partitioned data, and coordination, but scales near-linearly.
Convert Daily Active Users into average requests-per-second, then apply a peak multiplier (typically 2-3x average) to size infrastructure for realistic traffic spikes, not just the daily mean.
Estimate storage growth per year from (writes/day x avg payload size x 365) and replication factor, and estimate bandwidth from (requests/sec x avg response size) to size network and CDN egress budgets.
Most consumer systems are read-heavy (100:1 or higher read:write ratios), which justifies aggressive caching and read replicas; write-heavy systems (analytics, IoT ingestion) instead prioritize sharding, batching, and log-structured storage.
- Round numbers aggressively (10M DAU, not 9.7M) — precision doesn't matter, order of magnitude does.
- State every assumption out loud: 'Assuming 20% of DAU are active concurrently, and average session generates 5 write requests...'
- Spending 15+ minutes on estimation minutiae instead of the 5-10 minutes interviewers expect.
- Computing storage or QPS and then never using the number again to justify a design decision.
2. CAP Theorem, Consistency Models & Data Partitioning
Distributed data is where system design interviews separate candidates who've memorized buzzwords from those who understand the trade-offs of running a database across unreliable networks.
During a network partition, a distributed system must choose Consistency (reject/queue conflicting writes) or Availability (serve possibly-stale data); Partition tolerance is not optional in any real multi-node system, so the real choice is CP vs AP.
Even without a partition (Else), you still trade Latency against Consistency: synchronous cross-region replication lowers staleness but raises write latency; async replication does the opposite.
Strong consistency guarantees every read sees the latest write (Spanner, single-leader RDBMS); eventual consistency (DynamoDB, Cassandra default) allows temporary divergence; causal consistency preserves cause-effect ordering without full linearizability.
Range-based sharding (simple, prone to hotspots on sequential keys), hash-based sharding (uniform distribution, harder range queries), and directory-based sharding (a lookup service maps keys to shards, flexible but adds a dependency).
Leader-follower (single write path, simple consistency, follower lag risk), multi-leader (write availability across regions, needs conflict resolution), and leaderless/quorum-based (Dynamo-style, tunable via W+R>N).
// Consistent hashing distributes shard/cache keys so that adding or
// removing a node only remaps ~1/N of keys, instead of a full rehash.
class ConsistentHashRing {
private ring = new Map<number, string>();
private sortedHashes: number[] = [];
constructor(private readonly virtualNodesPerNode: number = 150) {}
private hash(key: string): number {
let h = 0;
for (let i = 0; i < key.length; i++) {
h = (Math.imul(h, 31) + key.charCodeAt(i)) >>> 0; // 32-bit unsigned
}
return h;
}
addNode(nodeId: string): void {
for (let v = 0; v < this.virtualNodesPerNode; v++) {
this.ring.set(this.hash(`${nodeId}#${v}`), nodeId);
}
this.sortedHashes = Array.from(this.ring.keys()).sort((a, b) => a - b);
}
removeNode(nodeId: string): void {
for (let v = 0; v < this.virtualNodesPerNode; v++) {
this.ring.delete(this.hash(`${nodeId}#${v}`));
}
this.sortedHashes = Array.from(this.ring.keys()).sort((a, b) => a - b);
}
// Finds the first node clockwise from the key's hash position.
getNode(key: string): string {
if (this.sortedHashes.length === 0) throw new Error("Ring has no nodes");
const target = this.hash(key);
let lo = 0, hi = this.sortedHashes.length - 1;
if (target > this.sortedHashes[hi]) return this.ring.get(this.sortedHashes[0])!;
while (lo < hi) {
const mid = (lo + hi) >>> 1;
if (this.sortedHashes[mid] < target) lo = mid + 1;
else hi = mid;
}
return this.ring.get(this.sortedHashes[lo])!;
}
}- Name the specific consistency guarantee your design needs per data type — e.g., strong consistency for payment ledgers, eventual consistency for like counts.
- When justifying a shard key, explicitly check for hotspot risk (e.g., sharding by date creates a hot 'today' shard).
- Claiming a design is 'always consistent and always available' without acknowledging the CAP trade-off during partitions.
- Picking a shard key based on convenience (auto-increment ID) instead of query access patterns.
3. Load Balancing, Caching, Message Queues & Rate Limiting
This is the operational core of nearly every HLD answer: how traffic is distributed, how reads are made fast, how work is decoupled from the request path, and how abuse is contained.
Layer 4 (transport) load balancers route based on IP/port with minimal overhead and high throughput; Layer 7 (application) load balancers inspect HTTP headers/paths, enabling content-based routing, SSL termination, and richer health checks at slightly higher latency.
Cache-aside (app checks cache, falls back to DB, then populates cache) is the most common; write-through (write to cache and DB synchronously) simplifies reads at write-latency cost; write-back (write to cache, flush to DB asynchronously) is fastest but risks data loss on cache failure.
When a hot key expires, thousands of concurrent requests can miss simultaneously and hammer the database; mitigate with request coalescing (single-flight), staggered TTLs with jitter, or serving stale-while-revalidate.
Kafka (high-throughput, ordered log, replayable, ideal for event streaming/analytics) vs RabbitMQ/SQS (simpler push-based work queues with per-message ack); use queues to decouple slow or bursty work (email, video encoding, notifications) from the synchronous request path.
Token bucket (allows controlled bursts, smooth refill), leaky bucket (strictly smooths output rate), fixed window counter (simple but allows 2x burst at window boundaries), and sliding window log/counter (accurate but more memory/compute per request).
How a typical read request is served with minimal database load in a scalable web tier.
// Allows short bursts up to 'capacity' while enforcing a long-term
// average rate of 'refillRatePerSecond' tokens/sec.
class TokenBucketLimiter {
private tokens: number;
private lastRefillMs: number;
constructor(
private readonly capacity: number,
private readonly refillRatePerSecond: number
) {
this.tokens = capacity;
this.lastRefillMs = Date.now();
}
private refill(): void {
const now = Date.now();
const elapsedSeconds = (now - this.lastRefillMs) / 1000;
if (elapsedSeconds <= 0) return;
const tokensToAdd = elapsedSeconds * this.refillRatePerSecond;
this.tokens = Math.min(this.capacity, this.tokens + tokensToAdd);
this.lastRefillMs = now;
}
tryConsume(tokensRequested: number = 1): boolean {
this.refill();
if (this.tokens >= tokensRequested) {
this.tokens -= tokensRequested;
return true; // Allow request
}
return false; // Reject with HTTP 429
}
}- Justify queue choice by delivery semantics needed: at-least-once with idempotent consumers is usually sufficient; exactly-once is expensive and rarely truly necessary.
- For rate limiting, explicitly state where enforcement happens (API gateway edge vs per-service) and what happens on limiter-store unavailability (fail-open vs fail-closed).
- Adding Kafka to every design regardless of whether the workload actually needs asynchronous decoupling or ordering guarantees.
- Forgetting to define cache invalidation strategy, leading to silent staleness bugs the interviewer will probe on.
4. The 4-Step HLD Framework & Designing Classic Systems
With the building blocks internalized, the framework below gives you a repeatable structure to apply them under 45 minutes of interview pressure, illustrated with three systems interviewers ask about constantly.
Pin down functional requirements (which user actions matter most) and non-functional requirements (scale in DAU, latency targets, availability target like 99.9%, read:write ratio) before drawing anything.
Compute QPS/storage/bandwidth, then sketch the top-level block diagram (client -> gateway/LB -> services -> cache/DB -> queue) with just enough boxes to anchor the deep-dive.
Pick the 2-3 hardest components (usually data model, sharding key, and one tricky feature) and go deep: schema, indexes, consistency guarantees, and API contracts.
Proactively call out single points of failure, hot shards, cascading failure risk, and how monitoring/alerting would catch degradation before users do.
Base62-encode an auto-incrementing or Snowflake-style distributed ID into a 7-character code (62^7 ≈ 3.5 trillion combinations); store the mapping in a key-value store sharded by hash of the short code, cache hot redirects aggressively (redirects are extremely read-heavy), and use HTTP 301 vs 302 deliberately to control analytics vs browser caching.
Persistent WebSocket connections held by stateful gateway servers, a presence service backed by a fast key-value store, messages written to a per-conversation log and fanned out to online recipients in real time while offline recipients get push notifications; message ordering and delivery receipts require per-conversation sequence numbers, not global ones.
Push (fan-out-on-write) precomputes feeds into each follower's inbox at post time — fast reads, expensive for celebrities with millions of followers; pull (fan-out-on-read) merges sources at request time — cheap writes, slower reads; production systems use a hybrid: push for normal users, pull-and-merge for high-follower accounts.
- Say the trade-off out loud even when you commit to one option: 'I'll fan out on write for most users, but pull for celebrity accounts to avoid a write amplification blowup.'
- Keep a mental timer — if Step 3 is running long, tell the interviewer you're time-boxing and ask which component they'd like prioritized.
- Jumping directly to 'we'll use microservices and Kafka' before establishing what the functional requirements actually demand.
- Never returning to Step 1's stated requirements to check whether the final design actually satisfies them.
Scaling a Ride-Hailing Dispatch Service from 50K to 5M Daily Rides
A mid-size ride-hailing startup's dispatch service — matching riders to nearby drivers — was built as a single-region monolith backed by one PostgreSQL primary. As daily rides grew from 50K to a projected 5M within a year, p99 matching latency had climbed past 4 seconds and the database was CPU-saturated during evening peak hours.
- 1The team ran capacity estimation first: 5M rides/day implied roughly 350 peak matching-requests/sec, each requiring a geospatial query against active drivers — far beyond what the single Postgres primary could sustain.
- 2They introduced geo-sharding: the city map was partitioned into hexagonal cells (using a system similar to Uber's H3), with driver location data sharded by cell ID across multiple database nodes to parallelize the geospatial lookups.
- 3A Redis-backed cache held each cell's currently-active driver set with a 3-second TTL, since driver positions change quickly but a few seconds of staleness was acceptable for the initial matching candidate pool.
- 4Driver location updates (previously synchronous writes to Postgres) were rerouted through a Kafka topic partitioned by cell ID, decoupling the high-frequency GPS ping ingestion from the matching read path.
- 5They added a circuit breaker around the matching service so that if the geospatial store degraded, the system fell back to a coarser, cached city-wide driver list rather than failing every request.
- 6The team ran a 2-week shadow-traffic test, comparing the new sharded path against the legacy monolith before fully cutting over.
Top Must-Know Interview Questions & Model Answers
Q1: Walk through a back-of-the-envelope capacity estimation for a service with 100M DAU and a 20:1 read:write ratio.
- •100M DAU x 5 actions/day = 500M requests/day ≈ 5,800 average RPS; applying a 3x peak factor gives ~17,000 RPS at peak.
- •With a 20:1 read:write ratio, roughly 550 write RPS and 5,250 read RPS at peak, which directly justifies heavy read-replica and cache investment.
- •Storage: if each write is ~1KB and there are ~28M writes/day, that's ~28GB/day raw, before replication factor (commonly 3x) and index overhead.
Q2: Explain CAP theorem and place DynamoDB, Cassandra, and a single-leader PostgreSQL setup on the CAP spectrum.
- •DynamoDB and Cassandra default to AP: they stay available during partitions by serving potentially stale replicas, using tunable quorums to shift toward consistency if needed.
- •A single-leader PostgreSQL setup with synchronous replication behaves as CP: it rejects writes rather than risk divergence when the leader can't reach a quorum of followers.
- •In practice, most systems are 'CP or AP with tunable knobs' rather than a fixed point — DynamoDB, for instance, offers both eventually-consistent and strongly-consistent reads.
Q3: What is PACELC and how does it extend CAP theorem for the non-partition case?
- •Synchronous cross-region replication minimizes staleness (higher consistency) but adds round-trip latency to every write.
- •Asynchronous replication cuts write latency dramatically but allows followers to lag, risking stale reads even without any partition occurring.
- •This explains why systems like Cassandra (PA/EL) and Spanner (PC/EC) make fundamentally different latency/consistency trade-offs even when the network is healthy.
Q4: Compare strong consistency, eventual consistency, and causal consistency with concrete examples.
- •Strong consistency (Spanner's TrueTime, single-leader synchronous replication) is required for account balances and inventory counts.
- •Eventual consistency (DynamoDB default, Cassandra) is acceptable for social media like-counts or view-counts where a few seconds of staleness is invisible to users.
- •Causal consistency ensures a reply to a comment is never shown before the comment itself, even if the two are stored on different replicas — critical for chat and comment threads.
Q5: Design a database sharding strategy for a multi-tenant SaaS product with wildly uneven tenant sizes.
- •Pure hash-based sharding by tenant ID risks co-locating several large tenants on the same shard by chance, creating a hotspot.
- •A directory service mapping tenant_id -> shard_id allows manual or automated rebalancing of specific tenants without a global rehash.
- •Track per-shard load metrics and set a threshold to trigger tenant migration to a dedicated shard before it becomes a bottleneck.
Q6: How does consistent hashing minimize data movement compared to naive modulo hashing when nodes are added or removed?
- •With modulo hashing, changing N from 4 to 5 changes the target shard for almost all keys, causing a massive, disruptive data migration.
- •Consistent hashing assigns each node (and virtual replicas of it) a position on a hash ring; a key belongs to the next node clockwise, so adding/removing one node only affects its immediate neighbors' key ranges.
- •Virtual nodes (100-200 per physical node) prevent uneven load distribution that would otherwise occur with only one ring position per node.
Q7: Compare leader-follower, multi-leader, and leaderless (quorum-based) replication topologies.
- •Leader-follower (PostgreSQL, MySQL): all writes go through one leader, followers replicate asynchronously or synchronously; simple mental model but the leader is a write bottleneck and failover requires promotion.
- •Multi-leader: useful for multi-region active-active writes, but requires conflict resolution (last-write-wins, CRDTs, or application-level merge logic) since two leaders can accept conflicting writes.
- •Leaderless (DynamoDB, Cassandra): writes go to any replica; a write succeeds once W replicas ack, a read queries R replicas; setting W + R > N guarantees overlap and strong-ish consistency.
Q8: A read replica in your leader-follower setup is lagging by 4 seconds. What user-facing problems does this cause, and how do you fix them?
- •Symptom: a user updates their profile, refreshes, and sees old data because the read hit a lagging replica.
- •Fix 1: route a user's own reads to the leader (or a replica known to be caught up) for a short window after their write ('read-your-writes' stickiness).
- •Fix 2: attach a version/timestamp to writes and have the client/read path wait for a replica whose applied version meets that watermark.
- •Fix 3: reduce lag itself via faster replication (semi-synchronous), smaller transactions, or dedicated replication bandwidth.
Q9: What's the difference between L4 and L7 load balancing, and when would you choose each?
- •L4 (e.g., a basic TCP/UDP load balancer) is ideal for raw throughput scenarios like a high-volume game server or a simple internal service mesh hop.
- •L7 (e.g., NGINX, ALB) can route /api/video traffic to a different fleet than /api/search, terminate SSL, and perform content-aware health checks.
- •Most consumer-facing web architectures use L7 at the edge for routing flexibility and L4 (or none) between internal trusted services for speed.
Q10: Compare cache-aside, write-through, and write-back caching strategies.
- •Cache-aside: app checks cache, on miss reads DB and populates cache; simple, but the first request after expiry is always slow, and cache/DB can briefly diverge.
- •Write-through: every write updates cache and DB together synchronously; reads are always fresh, but write latency increases and unused data still gets cached.
- •Write-back: writes go to cache immediately and are flushed to DB asynchronously in batches; excellent write latency and reduced DB load, but a cache node crash before flush can lose data.
Q11: How do you prevent a cache stampede (thundering herd) when a hot key expires?
- •Without protection, thousands of requests missing simultaneously on a hot key can all hit the database at once, potentially causing an outage.
- •Single-flight/request-coalescing: the first miss acquires a per-key lock and fetches from DB; concurrent misses wait on that in-flight fetch instead of issuing duplicate queries.
- •TTL jitter (base TTL +/- random 10%) prevents many keys from expiring in the exact same millisecond after a cold cache warm-up.
- •Stale-while-revalidate: serve the expired-but-still-cached value immediately while one background request refreshes it, trading brief staleness for zero request-path latency spikes.
Q12: Design a rate limiter that must work consistently across a fleet of 50 stateless application servers.
- •In-memory per-server counters are wrong at scale — a client could get N requests per server, effectively 50xN total, since state isn't shared.
- •Store a token bucket or sliding-window counter keyed by client ID in Redis; execute the check-and-decrement as a single atomic Lua script to avoid race conditions between concurrent app servers.
- •For very high throughput, an approximate local-then-global scheme (each server gets a local sub-budget synced periodically) trades strict accuracy for lower Redis load.
Q13: Compare token bucket, leaky bucket, fixed window, and sliding window rate limiting algorithms.
- •Token bucket: tokens refill continuously up to a capacity; bursts are allowed as long as tokens are available, which suits bursty client behavior like page loads.
- •Leaky bucket: requests are queued and processed at a constant rate, smoothing bursts entirely but adding queueing delay.
- •Fixed window counter: simplest to implement, but a client can send 2x the limit by timing requests around a window boundary (end of one window + start of next).
- •Sliding window log/counter: tracks exact timestamps (log) or a weighted blend of adjacent windows (counter) for much more accurate enforcement at higher memory/compute cost.
Q14: When would you choose Kafka over a traditional message queue like RabbitMQ or SQS?
- •Kafka retains messages in an ordered, partitioned log for a configurable retention period, so new consumers (e.g., a new analytics pipeline) can replay history — RabbitMQ typically deletes messages once acknowledged.
- •Kafka partitions provide ordering guarantees only within a partition, so choosing a good partition key (e.g., user ID) matters for use cases needing per-entity ordering.
- •RabbitMQ's per-message routing (exchanges, bindings) and easy dead-letter queue support make it simpler for classic task-queue workloads like 'send this one email.'
Q15: How do you achieve exactly-once-like processing semantics when your message queue only guarantees at-least-once delivery?
- •At-least-once delivery means a consumer crash after processing but before acknowledging will cause redelivery of an already-handled message.
- •Attach a unique idempotency key (e.g., a UUID generated by the producer) to each message; the consumer checks a durable store (DB unique constraint or dedupe cache) before applying the effect.
- •For financial operations, this typically means an 'idempotency_keys' table with a unique constraint, inserted transactionally alongside the business write.
Q16: How would you design a globally distributed URL shortener handling 500M new URLs per month and a 100:1 read:write ratio?
- •500M/month ≈ 190 writes/sec average; with 100:1 read:write, that's ~19,000 reads/sec average — heavily read-dominated, justifying an aggressive caching layer.
- •Avoid a single auto-increment counter (write bottleneck, single point of failure); instead use a Snowflake-style ID (timestamp + machine ID + sequence) generated independently per node, then base62-encode it.
- •Store short_code -> long_url in a key-value store sharded by hash of short_code; cache the hottest redirects in Redis/CDN edge, since redirect lookups are the overwhelming majority of traffic.
- •Use HTTP 302 (temporary redirect) by default so click analytics keep flowing through your service, rather than 301 which browsers cache permanently and bypass your servers.
Q17: In a URL shortener, how do you prevent short-code collisions without a global lock on ID generation?
- •A naive random 7-character code has a birthday-paradox collision risk that grows quickly at billions of scale, requiring a DB uniqueness check-and-retry loop that adds latency.
- •Snowflake IDs (timestamp bits + machine/worker ID bits + sequence bits) are globally unique by construction with no coordination needed between nodes.
- •Alternative: a central 'key range allocator' hands each app server a block of 1M pre-approved unique IDs to consume locally before requesting the next block.
Q18: Design the real-time messaging architecture for a WhatsApp-style chat application at 200M DAU.
- •Clients hold a persistent WebSocket to a connection-gateway tier; a connection registry (e.g., Redis) maps user_id -> gateway_node so any service can locate where to deliver a message.
- •A message is written durably to a per-conversation log first (source of truth), then the sender's gateway pushes it to the recipient's gateway if online, or triggers a push notification via APNs/FCM if offline.
- •Ordering and delivery receipts (sent/delivered/read) require a per-conversation monotonic sequence number, not a global one, since global ordering across unrelated conversations is unnecessary and expensive to coordinate.
- •Group chats fan out the write to each member's inbox/log; very large groups need the same push-vs-pull trade-off used in news feed design.
Q19: How do you show accurate 'online/offline' presence for hundreds of millions of chat users without overwhelming your infrastructure?
- •Each connected client sends a heartbeat every 10-30 seconds; the gateway refreshes a TTL-based key (e.g., in Redis) — absence of a heartbeat naturally expires presence to 'offline' without an explicit disconnect event.
- •Never broadcast presence changes to all users; only notify users who have that person in an active conversation or contact list, dramatically reducing fan-out volume.
- •Debounce rapid online/offline flapping (e.g., due to spotty mobile connectivity) by delaying the 'offline' broadcast by a few seconds in case the client reconnects.
Q20: Design a news feed system: compare fan-out-on-write (push) vs fan-out-on-read (pull) and when to use a hybrid.
- •Fan-out-on-write: when a user posts, the post is inserted into every follower's precomputed feed/inbox; reads are then just a fast lookup, ideal for the 99% of users with a normal follower count.
- •Fan-out-on-read: feeds are assembled on demand by querying and merging each followed account's recent posts; avoids the write-amplification disaster of a celebrity with 50M followers triggering 50M feed writes.
- •Hybrid (used by Twitter/Instagram at scale): push for regular accounts, but celebrity/high-follower posts are excluded from push fan-out and instead merged in at read time for any follower's feed request.
- •Ranking (not just chronological order) typically happens at read time regardless of fan-out strategy, using a separate ranking/ML service.
Q21: How would you paginate a news feed or search results API at scale without using OFFSET-based pagination?
- •OFFSET-based pagination requires the database to scan and discard all preceding rows, getting progressively slower on deep pages, and can show duplicate/missing items if rows are inserted between page requests.
- •Cursor pagination returns an opaque cursor (typically an encoded last-seen sort key value) that the next request uses in a WHERE clause (e.g., WHERE (created_at, id) < (cursor_time, cursor_id)) to fetch the next page directly via an index.
- •This requires a composite index on the sort columns and works well with infinite-scroll UIs, though it sacrifices the ability to jump directly to an arbitrary page number.
Q22: Compare 2-Phase Commit (2PC) and the Saga pattern for distributed transactions across microservices.
- •2PC: a coordinator asks all participants to 'prepare' (lock resources), then commits only if all vote yes; if the coordinator crashes after prepare, participants can be left blocked holding locks indefinitely.
- •Saga: each service performs its local transaction and publishes an event/command triggering the next step; if a later step fails, previously completed steps are undone via explicit compensating transactions (e.g., refund payment after inventory reservation fails).
- •Sagas are either choreography-based (services react to each other's events, no central coordinator) or orchestration-based (a central saga orchestrator issues each step and handles failures).
Q23: What is the outbox pattern and what problem does it solve when publishing events after a database write?
- •Writing to the database and publishing to Kafka/RabbitMQ as two separate operations risks one succeeding and the other failing (e.g., DB commits but the broker publish fails, or vice versa), silently losing events.
- •The outbox pattern inserts an 'event to publish' row into an outbox table within the same ACID transaction as the business write, guaranteeing atomicity.
- •A separate relay (polling the outbox table, or using change-data-capture like Debezium reading the DB write-ahead log) asynchronously publishes outbox rows to the broker and marks them sent.
Q24: Explain the difference between a B-tree index and an LSM-tree, and when each is preferred.
- •B-trees (PostgreSQL, MySQL InnoDB) update pages in place, giving good point-lookup performance but requiring random disk I/O on writes, which becomes a bottleneck under very high write volume.
- •LSM-trees (Cassandra, RocksDB, HBase) buffer writes in an in-memory memtable, flush to immutable sorted SSTables sequentially, and periodically compact them — this converts random writes into sequential ones, dramatically improving write throughput.
- •The trade-off is read amplification: a read may need to check the memtable and multiple SSTables, mitigated with Bloom filters to quickly skip SSTables that can't contain a key.
Q25: How would you design a distributed lock, and what pitfalls exist with a naive Redis SETNX-based implementation?
- •A naive SET key value NX EX ttl acquires a lock atomically with auto-expiry (preventing permanent deadlock if the holder crashes), but releasing it must verify ownership (e.g., compare-and-delete via a Lua script) to avoid releasing another client's lock.
- •A single Redis instance is a single point of failure and can lose the lock state on failover, letting two clients believe they hold the same lock simultaneously.
- •Redlock acquires the lock against a majority of N independent Redis nodes within a bounded time; it's a partial mitigation, though it remains a debated approach for correctness under clock drift and GC pauses.
- •For strict correctness guarantees, a consensus system like ZooKeeper or etcd (using sequential ephemeral nodes) is generally preferred over ad hoc Redis locking.
Q26: At a high level, how does the Raft consensus algorithm elect a leader and replicate a log entry?
- •Each node has a randomized election timeout; if no heartbeat arrives from a leader in time, it becomes a candidate, increments its term, and requests votes from peers.
- •A candidate becomes leader once it receives votes from a majority of nodes; term numbers prevent stale leaders from a network partition from overriding a newer leader.
- •The leader appends new entries to its log and replicates them to followers; an entry is considered committed once a majority of nodes have persisted it, at which point it's safe to apply and respond to the client.
Q27: How would you design a multi-region active-active architecture, and how do you handle conflicting concurrent writes to the same record?
- •Active-active reduces write latency for users in each region and improves availability (a whole region can go down without a global outage), but multiple regions can accept conflicting writes to the same key concurrently.
- •Last-write-wins (LWW) using synchronized clocks (or hybrid logical clocks) is simple but can silently discard a legitimate concurrent update.
- •CRDTs (Conflict-free Replicated Data Types) allow certain data structures (counters, sets) to merge deterministically without data loss, at the cost of restricting the data model.
- •For strictly consistent data (e.g., financial ledgers), active-active is often avoided in favor of a single-region-write, multi-region-read topology instead.
Q28: What SLIs, SLOs, and monitoring would you propose for a newly designed payments API?
- •SLI examples: percentage of successful (2xx/4xx expected) responses, p50/p95/p99 latency, and queue depth for async payment processing.
- •SLOs translate SLIs into targets tied to business impact (e.g., 99.95% success over 30 days) and define an error budget that, once exhausted, should pause risky deploys.
- •Instrumentation needs all three pillars: metrics (aggregated dashboards/alerts), structured logs (debugging specific failed transactions), and distributed traces (following one request across payment, fraud-check, and ledger services).
Q29: How do you decide between a monolith and microservices for a new product, and what does 'premature microservices' cost a team?
- •Microservices genuinely help when independent teams need to deploy independently, or when components have wildly different scaling/resource profiles that benefit from isolated scaling.
- •Premature adoption forces every feature to cross network boundaries, introduces distributed transaction complexity (see Saga/2PC), and multiplies the operational surface area (service discovery, per-service monitoring, versioned contracts) for a team too small to absorb it.
- •A common pragmatic path is 'modular monolith first' — enforce clean internal module boundaries so a future extraction into services is a refactor, not a rewrite.
Q30: Design a global CDN caching strategy for a video streaming platform's static assets and thumbnails.
- •Content-hash the asset filename (e.g., thumbnail.a1b2c3.jpg) so any content change produces a new URL, allowing an effectively infinite TTL without stale-content risk.
- •Use a tiered cache hierarchy (edge PoP -> regional cache -> origin) so a cache miss at the edge doesn't always travel all the way back to origin, reducing origin load and cross-region latency.
- •Never cache personalized or authenticated responses at a shared CDN layer unless using private, per-user cache keys — otherwise one user's cached response can leak to another.
Q31: What's the difference between vertical partitioning and horizontal partitioning (sharding), and when would you use vertical partitioning first?
- •Vertical partitioning moves large or infrequently accessed columns (e.g., a user's biography text, profile image blob) into a separate table, keeping the hot row narrow and cache-friendly.
- •It can also mean splitting a database by service/domain (users DB, orders DB, inventory DB) — a natural precursor to a services architecture.
- •Horizontal sharding is reached for once a single machine can no longer hold the working set or handle the write throughput of one logical table, requiring a shard key and routing logic.
Mistakes That Sink Otherwise Strong Candidates
Why it happens: Candidates feel pressure to demonstrate architecture knowledge immediately and treat clarifying questions as wasted time.
The fix: Spend the first 5 minutes explicitly listing functional requirements and non-functional targets (scale, latency, availability) — every later decision should trace back to one of these.
Why it happens: Estimation is treated as a ritual checkbox rather than an input that should actually change architecture decisions.
The fix: Explicitly connect each number to a decision: 'At 17K peak RPS, a single DB instance can't serve this, so we need read replicas and caching.'
Why it happens: Buzzword-driven design feels impressive and mirrors what candidates read in big-tech engineering blogs, regardless of actual scale requirements.
The fix: Justify every additional piece of infrastructure against the stated scale; explicitly say when a simpler option (a monolith, a single sharded DB) is sufficient.
Why it happens: CAP theorem is often memorized as a slogan rather than internalized as a real engineering constraint.
The fix: For every stateful component, explicitly state what happens during a network partition or node failure, and which property (C or A) is sacrificed.
Why it happens: It feels like a minor implementation detail rather than a decision the interviewer expects to be justified.
The fix: Choose the shard key based on the dominant access pattern (e.g., shard by user_id if 90% of queries filter by user) and explicitly check for hotspot risk.
Why it happens: Caching is treated as a free performance win rather than a source of a whole new class of staleness and thundering-herd bugs.
The fix: Always pair 'we'll cache X' with an explicit invalidation strategy, TTL policy, and stampede mitigation (jitter, single-flight, or stale-while-revalidate).
Why it happens: Time pressure pushes candidates to focus entirely on the happy path and leave resilience for 'if there's time.'
The fix: Reserve the final 10 minutes specifically for SPOF and failure-mode discussion, even if it means cutting a deep-dive short.
Why it happens: Nervousness leads candidates to stick rigidly to a memorized design template regardless of the signals they're given.
The fix: Pause after each major decision to check in ('Does this direction make sense, or would you like me to focus elsewhere?') and actually adapt based on the answer.
Quick-Reference Cheat Sheet
Recommended Practice Quizzes on QuizCluster
Test your retention and prepare for timed live coding and MCQ technical screening rounds:
High-Level System Design (HLD)
Scenario-based drills on load balancing, sharding, caching, CAP trade-offs, and rate limiter design under real interview constraints.
Low-Level Design (LLD) & SOLID
Practice translating high-level architecture into clean class hierarchies, interfaces, and SOLID-compliant object models.
SQL Optimization & Transaction Isolation
Reinforce the indexing, sharding, replication, and isolation-level knowledge that underpins every data-layer deep-dive.
Frequently Asked Questions
Do I need to write actual code in a system design interview?
Rarely, and only in small, targeted amounts — e.g., a core API signature, a rate limiter's check function, or a database schema DDL. The interview is primarily evaluating architecture, trade-off reasoning, and communication, not implementation.
How much time should I spend on back-of-the-envelope math?
5-10 minutes out of a 45-minute round. The goal is to derive numbers that inform later decisions (e.g., 'this justifies sharding' or 'this doesn't need a queue yet'), not to produce perfectly precise figures.
Is it better to go broad across many components or deep on just one or two?
Depth wins. Interviewers consistently rate candidates higher when they pick 2-3 components (usually the data model, a sharding/consistency decision, and one tricky feature) and reason through them thoroughly, rather than superficially naming every buzzword.
What's the single most common reason strong engineers fail system design rounds?
Skipping the requirements-clarification step and jumping straight to a familiar architecture (usually microservices plus Kafka) without confirming it actually matches the stated scale and constraints of the problem.