At a Glance
- Use the 5-step framework: Requirements → Estimation → High-level design → Deep dive → Bottlenecks. Never skip step 1.
- Requirements first, always. A wrong scope wastes 35 minutes and costs the offer.
- State numbers out loud. 10M DAU → 116 QPS. That number should trigger your next design decision.
- Six building blocks cover 80% of questions: LB + cache + DB replica + queue + CDN + stateless app servers.
- Name every trade-off. "Cache gives 10x reads at ≤5 min staleness." Say that sentence shape, constantly.
- Start simple, earn complexity. Monolith → replicas → cache → shard → queue. Each step needs a justifying number.
The Five-Step Framework
A system design interview is 45 minutes of deliberate ambiguity — the interviewer wants to watch you impose structure on it. This framework always works:
Budget your 45 minutes deliberately — running out of time in step 3 is the most common failure.
Functional: what must it do? (shorten URLs, redirect, custom aliases?) Non-functional: how well? (scale, latency targets, availability vs consistency, durability). Explicitly scope things OUT: "I'll skip analytics for now — fair?" Interviewers love hearing what you're not building.
DAU → QPS (recall: 1M/day ≈ 12 QPS), read:write ratio, storage per year, bandwidth. The output isn't the number — it's the decisions it triggers: "600 reads/sec means caching matters more than write scaling."
Client → DNS/CDN → LB → app servers → cache → DB → queue → workers. Draw the simple, correct skeleton first and trace one request through it aloud. Don't gold-plate yet.
The interviewer (or you) picks 1–2 components: DB schema and shard key, the API contract, the core algorithm, the cache strategy. This is where Topics 06–08 knowledge gets spent.
Single points of failure, hot shards, cache stampedes, the DB write ceiling. Name each, propose the fix, state its cost. Closing strong here is the difference between "solid" and "senior."
Patterns to have loaded in cache: horizontal scaling + LB (05), CDN for static (04), cache-aside with Redis (06), read replicas → sharding (07), async via queues (08), and stateless services so any server handles any request (09). Most questions are these six blocks rearranged.
The Estimation Cheat Sheet
Burn these numbers in. Not to recite them — to derive architectural decisions from them automatically.
| Starting point | Conversion | Design implication |
|---|---|---|
| 1M DAU | ≈ 12 QPS (assume 8-hr active day) | Single app server likely sufficient |
| 10M DAU | ≈ 116 QPS | Caching needed; add read replicas |
| 100M DAU | ≈ 1,160 QPS | DB sharding + CDN essential |
| 1B DAU | ≈ 11,600 QPS | Multi-region; geo-routing; Kafka-scale queues |
| 1 tweet, 100M followers | 100M fan-out writes | Hybrid fan-out on read for celebrities |
| 100 bytes/msg × 100M msgs/day | 10 GB/day = 3.6 TB/yr | Object storage, not DB rows |
| Photo 200 KB × 1B photos | 200 TB total | S3/GCS + CDN; no database |
| Video 1 GB × 1M uploads/day | 1 PB/day raw | Dedicated media pipeline; async transcoding |
| Read:write = 100:1 | — | Cache-aside + read replicas; writes are cheap |
| SSD random read | ≈ 100 µs | In-memory cache = 1000× faster (≈ 100 ns) |
"I calculated 1,200 QPS on reads" is useless by itself. "1,200 QPS means a cold Postgres can handle it but we'll hit CPU limits around 50% load — so I'll add Redis cache-aside to absorb the hottest 10% of data and give us headroom" earns points.
The Six-Block Starter Kit
Almost every system design question is answered by assembling the same six building blocks in different orders. Internalize them; reach for them before reaching for anything fancy.
This skeleton answers URL shortener, chat, social feed, ride-sharing, and video streaming. Rearrange the blocks; the blocks stay the same.
Worked Example 1 — URL Shortener (TinyURL)
Requirements. Shorten a long URL to ~7 characters; redirect fast; links live for years; optional custom aliases. Non-functional: redirects <50 ms and highly available. Read-heavy: assume 100:1 reads to writes.
Estimates. 10M new URLs/month ≈ 4 writes/sec; 1B redirects/month ≈ 400 reads/sec. Storage: 10M/month × ~500 bytes ≈ 5 GB/month — 60 GB/year. This is a latency and availability problem, not a big-data problem. Say that out loud — it's the key insight.
The algorithm. Use a counter + base62 encoding (a–z, A–Z, 0–9). 627 ≈ 3.5 trillion codes — enough forever. Avoid hashing the URL alone (collisions, and two users shortening the same URL get the same code — fine until one wants analytics).
ALPHABET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
def encode(n: int) -> str:
s = ""
while n:
s = ALPHABET[n % 62] + s
n //= 62
return s or "a"
# DB: urls(id BIGINT PK, short_code CHAR(7) UNIQUE,
# long_url TEXT, created_at, expires_at)
Design. POST /shorten → app server gets a unique ID (a DB sequence, or pre-allocated ID ranges per server — Flickr's ticket-server trick), encodes it, stores the row. GET /{code} → check Redis (cache-aside; hot links are wildly skewed) → on miss, read DB, fill cache, reply 302 if you want analytics on every hit, 301 if not.
Scale & trade-offs. Cache absorbs ~90% of redirects; replicate DB for reads; CDN/edge can serve redirects. Mention abuse (rate-limit creation, blocklist malicious domains) and expiry cleanup as a background job (Topic 08).
| Decision | Option A | Option B | Winner & reason |
|---|---|---|---|
| ID generation | DB auto-increment | Pre-allocated ranges per server | B — removes counter bottleneck at scale |
| Redirect code | 301 Permanent | 302 Temporary | 302 if analytics needed (browser won't cache) |
| Cache strategy | Cache-aside (Redis) | Write-through | Cache-aside — write path is cold, only reads matter |
| Primary store | Postgres (SQL) | DynamoDB (NoSQL) | SQL — ACID enforces unique constraint on short_code |
Worked Example 2 — Social Feed (Twitter / Instagram)
The crux. Home timeline = merged recent posts from everyone you follow. Two opposite strategies:
Fan-out on write (push)
When Alice posts, push the post ID into every follower's pre-computed timeline list in Redis. Reading your feed is one cheap fetch. But a celebrity with 100M followers triggers 100M writes per post.
Fan-out on read (pull)
Store posts once; when Bob opens the app, pull recent posts from all 800 people he follows and merge. Writes are O(1); every feed read is an 800-way merge. Slow for active readers.
| Fan-out on write | Fan-out on read | Hybrid | |
|---|---|---|---|
| Write cost | O(followers) per post | O(1) | O(followers), capped |
| Read cost | O(1) — precomputed | O(following) merge | O(1) + celebrity merge at read time |
| Celebrity problem | 100M timeline writes | Fine | Merge celebrities at read time only |
| Production use | Most users' posts | Rarely alone | Twitter, Instagram feed system |
The hybrid. Fan-out on write for 99.9% of users. For celebrities (follower count above a threshold), skip fan-out — instead merge their fresh posts at read time. Each user's feed = pre-computed list + live merge of a handful of celebrities they follow. The threshold is a tunable knob.
Supporting cast. Posts in a sharded store keyed by post ID; timelines in Redis (lists capped ~800 entries); fan-out is async via queues (Topic 08) — a post appearing in feeds 2 seconds late is invisible, which is eventual consistency (Topic 03) earning its keep. Media on a CDN (Topic 04).
It has no right answer — it has a spectrum, and your job is to find the hybrid and justify the threshold. "Read-heavy favors precomputation; extreme fan-out breaks it; split users by fan-out cost" demonstrates the whole skill being tested.
Worked Example 3 — Chat System (WhatsApp / Slack)
Requirements. 1:1 and group messaging; delivery receipts (sent/delivered/read); offline message storage; scale to 500M DAU. 500M DAU × 40 msgs/day ≈ 230K writes/sec. Each message ~100 bytes → ~2 TB/day storage.
The protocol choice. Chat is bidirectional and persistent — this is the textbook WebSocket use case (Topic 09). One TCP connection per client, kept alive; server pushes messages without polling. For offline users, store messages and deliver on reconnect.
Kafka decouples senders from receivers. Redis session map routes messages to the right chat server. MongoDB holds offline messages with TTL.
Key decisions to voice. (1) Session registry in Redis: when Chat Server 1 gets Alice's message for Bob, it looks up Bob's server in Redis, then routes directly — or publishes to Kafka if Bob is on a different server. (2) Offline storage: if Bob is offline, messages go to MongoDB with a TTL; delivered on reconnect. (3) Message ordering: monotonically increasing IDs per conversation (Snowflake ID or sequence per chat room). (4) Group chat: same fan-out problem as Twitter — same hybrid approaches apply.
Trade-off to name: WebSocket servers are stateful (Bob must reconnect to a server that can reach him). Use sticky sessions (Layer 4 LB by IP hash) or put routing state in Redis so any server can route to any other. The Redis approach is cleaner at scale.
Worked Example 4 — Distributed Rate Limiter
Requirements. Limit each API key to N requests per window (100/min); return 429 with Retry-After when exceeded; add <5 ms latency; work across a fleet of API servers (a per-server counter would let clients get N × servers requests).
Algorithm: token bucket. Each key gets a bucket: capacity 100, refilling at 100/60 tokens per second. A request spends a token; an empty bucket means 429. Buckets allow honest bursts while capping the sustained rate — and need only two numbers stored per key: token count and last-refill timestamp.
-- KEYS[1]=bucket key · ARGV: capacity, refill_rate, now (unix seconds)
local tokens = tonumber(redis.call('HGET', KEYS[1], 'tokens') or ARGV[1])
local last = tonumber(redis.call('HGET', KEYS[1], 'ts') or ARGV[3])
tokens = math.min(ARGV[1], tokens + (ARGV[3] - last) * ARGV[2])
if tokens < 1 then return 0 end -- 429 Too Many Requests
redis.call('HSET', KEYS[1], 'tokens', tokens - 1, 'ts', ARGV[3])
return 1 -- allowed
Trade-offs to volunteer. Redis is now on the hot path — if it dies, do you fail open (allow everything; risk abuse) or fail closed (block everything; outage)? Most choose fail-open with alerts. At extreme scale, approximate local buckets sync to Redis periodically — slightly leaky limits for much lower latency. Enforce at the API gateway (Topic 10) — one place, every service protected.
| Algorithm | Burst handling | Memory cost | Boundary burst? |
|---|---|---|---|
| Token bucket | Allows bursts up to capacity | 2 values/key | No |
| Leaky bucket | No burst — smooths output | Queue per key | No |
| Fixed window counter | 2x burst at window edge | 1 counter/key | Yes |
| Sliding log | Precise, no boundary burst | O(N) per key | No |
| Sliding window counter | Near-precise, O(1) memory | 2 counters/key | Minimal |
Pattern Trigger Phrases — Cheat Sheet
When the interviewer says the magic words, reach for these patterns automatically.
| Trigger phrase | Reach for | Topic |
|---|---|---|
| "very fast reads, slight staleness OK" | Cache-aside with Redis + TTL | 06 |
| "don't lose a single record" | WAL + synchronous replication + at-least-once delivery | 07, 08 |
| "millions of users, one region" | CDN + edge caching + geo-routing LB | 04, 05 |
| "global low latency" | Multi-region active-active + CRDTs | 02, 03 |
| "handle traffic spikes" | Queue + worker pool + auto-scaling | 08 |
| "decouple services" | Event queue (Kafka/SQS) + idempotent consumers | 08, 10 |
| "100M+ rows, single table hot" | Horizontal sharding by user_id hash | 07 |
| "real-time bidirectional comms" | WebSocket with session routing in Redis | 09 |
| "server-push to many clients" | Server-Sent Events; Kafka fan-out to push servers | 08, 09 |
| "cross-service transaction" | Saga pattern (orchestrated via Temporal) | 10 |
| "secure internal service comms" | mTLS via service mesh (Istio / Linkerd) | 10, 11 |
| "complex access patterns on large data" | DynamoDB / Cassandra with pre-modeled access paths | 07 |
Interview Q&A
Questions that have surprised candidates — click to reveal strong answers.
Five-nines is ~5 minutes of downtime per year. You can't achieve it with one of anything. Checklist: (1) No single points of failure — every layer redundant: multiple app servers behind a LB, multi-AZ database, replicated cache. (2) Active-active multi-region — one region dying should be invisible. (3) Health checks + auto-restart — bad instances removed from rotation in <30s. (4) Graceful degradation — serve stale cache when the DB is down. (5) Chaos engineering — confirm the redundancy actually works before you need it. (6) Zero-downtime deploys — blue-green or canary releases. Trade-off: active-active requires conflict resolution (CRDTs or last-write-wins), which may sacrifice strict consistency.
In order of increasing complexity: (1) Connection pooling (PgBouncer) — removes per-connection overhead. (2) Write batching — batch inserts, async writes via queue. (3) Vertical scaling — more CPU/RAM, faster SSDs. (4) Functional partitioning — put users and orders on separate DB clusters. (5) Horizontal sharding — shard by user_id hash; each shard owns a subset. (6) Write-optimized store — Cassandra's LSM-tree is faster than B-tree for heavy writes. Give the choice a number: "At 10K writes/sec, sharding into 16 shards gives 625 writes/sec/shard — well within Postgres's sweet spot."
SQL by default. Choose NoSQL when: (1) Schema-less flexibility — MongoDB for user profiles with varying fields. (2) Massive horizontal write scale — Cassandra for write-heavy time-series at 100K+ writes/sec. (3) Key-value at ultra-low latency — DynamoDB for sessions and shopping carts. (4) Graph relationships — Neo4j for social graphs or fraud rings. (5) Full-text search — Elasticsearch. The answer interviewers want: "SQL by default; NoSQL when I can predict access patterns up front, because NoSQL requires pre-modeling your queries and punishes ad-hoc joins."
SQL LIKE with a leading wildcard (LIKE '%foo') can't use a B-tree index — full table scan. Add a dedicated search index. (1) Elasticsearch: an inverted index mapping terms to document IDs. Near-real-time (≈1s lag). (2) Sync strategy: dual-write (simple, risks partial failure), change data capture from WAL via Debezium → Kafka → ES consumer (reliable, slight lag), or periodic batch reindex (simple but stale). (3) Relevance tuning: BM25 scoring, field boosts, fuzzy matching, synonyms. (4) Scaling: shards and replicas — same CAP considerations as any distributed store. Elasticsearch is eventually consistent and not a source of truth — SQL DB is.
Mistakes That Cost Offers
Jumping to microservices / Kafka before requirements
Opening with "I'd use a microservices architecture with Kafka and Kubernetes" before requirements are clear signals cargo-cult engineering. Start simple. Earn complexity by pointing to a number that justifies it: "At 50K events/sec the queue becomes a bottleneck — that's when I'd introduce Kafka."
Silent design
Drawing boxes without narrating is a silent death — the interviewer can't evaluate what they can't see. Verbalize every choice and every rejected alternative. "I'm choosing Redis over Memcached here because I need sorted sets for the leaderboard, not just key-value."
Estimation without decisions
Calculating 1,200 QPS and moving on leaves points on the table. The number must trigger a decision: "1,200 reads/sec means a single DB can handle it, but I'll add Redis cache-aside to absorb the hottest 10% and give us 5× headroom for growth."
No trade-offs named
Every choice has a cost. "I'll use a cache" must be followed by "at the cost of up to N seconds of staleness." "I'll shard" must be followed by "which limits cross-shard joins and requires the shard key in every query." Naming trade-offs is what separates senior from mid-level.
Ignoring failure modes
Every system fails. Name it: what if the DB is down? What if a queue consumer crashes mid-processing? What if a cache server goes cold? Name the failure, name the mitigation (retry, fallback, idempotency key), and name the trade-off of that mitigation.
Interview Mindset & Final Preparation
Clearing a system design interview is as much about how you think and communicate as it is about knowing the right patterns. Interviewers are evaluating four things simultaneously: clarity of thought, depth in trade-offs and technical decisions, communication and collaboration skills, and structured problem-solving. Keeping those four lenses in mind shapes every minute of your session.
What Interviewers Are Really Measuring
- Clarity of thought — Can you decompose a vague prompt into concrete requirements without hand-waving?
- Trade-off depth — Do you know why you chose SQL over NoSQL, or strong consistency over eventual? Can you articulate the cost?
- Communication — Are you narrating your reasoning step-by-step, or just drawing boxes silently?
- Structured problem-solving — Do you follow a repeatable framework, or jump straight to implementation details?
The Right Mindset Going In
- Stay curious, not anxious — Treat the problem as an interesting puzzle, not a pass/fail test. Curiosity surfaces better questions.
- Prioritize exploration over perfection — No real system is perfect. Show that you understand trade-offs better than you chase a single "right" answer.
- Communicate step-by-step — Interviewers want to follow your reasoning. Say "I'm going to start by clarifying requirements" before you do it.
- Lean into complexity — When scope expands or constraints change, don't panic. Reassess bottlenecks aloud and adapt.
The 4-Step Repeatable Framework
Use this framework every time — it keeps responses structured and easy to iterate on when the interviewer adds new constraints:
- Step 1 — Understand requirements — Gather functional requirements (what the system does) and non-functional requirements (scale, latency, availability, consistency). Ask clarifying questions out loud.
- Step 2 — Estimate scale & identify bottlenecks — Back-of-the-envelope math: DAU, QPS, storage, bandwidth. Use this to pinpoint which components will buckle first.
- Step 3 — High-level design — Sketch the major components, data flow, and key service interactions. Don't over-engineer; get the skeleton right first.
- Step 4 — Strategic tech & infra decisions — Now go deep: database choice, caching strategy, async vs. sync, scaling approach, failure modes. Always justify your choices with context.
Handling Ambiguity and Evolving Scope
Interviewers add ambiguity on purpose. When the scope changes mid-interview: reassess bottlenecks, communicate how your design needs to shift, and reason through the impact. Practice decomposition — break large problems into services, flows, or layers, then zoom into specific parts (storage, API, auth) when the interviewer signals interest.
Communicating Trade-offs Effectively
- Always explain why X over Y — SQL vs. NoSQL, strong vs. eventual consistency, monolith vs. microservices, caching vs. precomputation. Context wins every time.
- Show awareness of real-world costs — Latency, cost, scalability, and availability pull in different directions. Name the tension.
- Avoid blanket answers — "Just use Kafka" without justification is a red flag. Tie every choice to the problem's specific constraints.
Building Interview Fluency
- Timed drills — Practice on whiteboards or tools like Excalidraw with a 45-minute timer. Record yourself and review for clarity.
- Mock sessions with peers — Nothing replaces real-time feedback from someone playing the role of interviewer.
- Build muscle memory — Use templates for requirements gathering and scale estimation. Repeat core design patterns until they become reflexive.
- Prepare for both formats — Remote (diagramming tools, screen sharing) and in-person (whiteboarding and verbal reasoning) require slightly different pacing.
Final thought: You are not just preparing for interviews — you are becoming a system thinker. The habits you build here (structured decomposition, trade-off reasoning, clear communication) will serve you throughout your engineering career.
Case Study: Ticketing System (BookMyShow)
Design an online platform where users can browse events, view real-time seat availability, book and pay for tickets, and receive confirmation — while the system handles high concurrency, flash sales, and the critical requirement of never double-booking a seat.
Functional Requirements:
- Browse & search events — Users can discover events, venues, and view seat maps with live availability.
- Reserve & book tickets — Seat selection, temporary hold (5-minute TTL), payment, and confirmation.
- Real-time seat availability — Seat status must reflect locks and bookings within milliseconds.
- Secure payment — Integrates with third-party gateways (Stripe, Razorpay) with retry and idempotency support.
- Email/SMS confirmations — Async notification after successful booking.
- Admin portal — Create/manage events, define seat layouts and pricing tiers.
Non-Functional Requirements:
- High availability — No downtime during peak flash sales; multi-region deployment.
- Low latency — Booking response in milliseconds; seat availability updates in near real-time.
- Scalability — Handle 5M total users, 100K concurrent users during peak events.
- Strong data consistency — Zero double-bookings; transactional guarantees on seat allocation.
- Audit trail — All transactions logged for fraud prevention and dispute resolution.
Scale Estimates: 1M DAU, 100K concurrent users at peak, ~500K bookings/day (~6/sec average, up to 2,000/sec during a major release). Primary bottlenecks: race conditions on seat allocation, write pressure on the booking DB, payment API latency, and notification queue buildup.
High-Level Architecture:
- API Gateway — Unified entry point; handles auth (OAuth2/JWT), rate limiting, and routing.
- Event Management Service — CRUD for events, venues, and seat layouts (stored in MongoDB/DocumentDB for flexible schemas).
- Seat Inventory Service — Tracks available/locked/booked seats; backed by Redis for millisecond-latency reads.
- Booking Service — Orchestrates the booking flow: lock seat → initiate payment → confirm booking (PostgreSQL for ACID guarantees).
- Payment Service — Integrates with Stripe/Razorpay; handles retries via idempotency keys and webhook callbacks.
- Notification Service — Async email/SMS via AWS SES and Twilio, fed by Kafka topics.
- Kafka — Decouples booking events from notifications, audit logs, and payment retries.
Key Design Decisions:
- Concurrency control — Use optimistic locking (version check on seat row) for low-contention scenarios; pessimistic locking (seat-level DB lock) during flash sales with extremely high contention.
- Seat hold timeout — Redis TTL-based lock (5-minute auto-release) prevents seats from being held indefinitely if payment is abandoned.
- CQRS pattern — Separate read models (seat availability, event listings cached in Redis) from write models (booking transactions in PostgreSQL) to prevent read traffic from degrading write performance.
- Idempotency keys for payments — Every payment request carries a unique key so retries never cause duplicate charges.
- Elasticsearch for event search — Full-text and geo-filtered event discovery without hammering the relational DB.
Scaling Strategy: Deploy on Kubernetes with Horizontal Pod Autoscaling; partition Kafka topics by event ID; use read replicas for the event catalog; deploy Redis Cluster for seat inventory; route to the nearest region via a global load balancer. For extremely popular events, queue incoming booking requests and process them in strict order to eliminate race conditions entirely.
Case Study: News Feed (Twitter / Facebook)
Design a social news feed where users post updates and followers see a personalized, real-time timeline. The core challenge is fan-out: one tweet from a user with one million followers must propagate instantly, efficiently, and without duplicates — at a scale of 500M users and 1B tweets per day.
Functional Requirements:
- Post a tweet/update — Short-form text with optional media (images, video).
- Follow/unfollow users — Directed social graph.
- View home timeline — Chronological or ranked feed of posts from followed users.
- Engagement — Likes, replies, retweets; counters must be accurate at scale.
- Media uploads — Chunked upload, stored in object storage, delivered via CDN.
- Real-time updates — New posts appear in timelines without a manual refresh.
Non-Functional Requirements:
- High availability — No single point of failure; timelines must load even if one region degrades.
- Low latency — Timeline reads must feel instant; target <100ms p99.
- Massive scalability — 500M total users, 200M DAU, 1B tweets/day, 2B+ feed requests/day.
- Read-heavy workload — ~80% of traffic is reads; optimize accordingly.
The Fan-Out Problem: When a user posts, every follower's timeline must update. Two models exist:
- Fan-out on write (push) — Pre-compute and write the tweet into every follower's timeline cache at post time. Fast reads, but a celebrity with 10M followers generates 10M writes per tweet.
- Fan-out on read (pull) — Compose the timeline dynamically at read time by fetching from followed users. Lighter writes, but slower and more expensive reads.
- Hybrid model (recommended) — Fan-out on write for regular users; fan-out on read for celebrity accounts (identified by follower threshold). Gives fast reads for most users without write storms from hot accounts.
High-Level Architecture:
- Tweet Service — Creates tweets, stores in Cassandra/DynamoDB (wide-column, high write throughput).
- Timeline Service — Composes home timelines; reads from pre-computed Redis caches or fetches live for high-follower-count users.
- Fanout Worker — Consumes tweet events from Kafka and propagates to follower timeline caches asynchronously.
- Engagement Service — Handles likes, retweets, replies; uses Redis counters for high-speed increments.
- Media Service — Chunked upload API, stores originals in S3/GCS, serves via CDN (Cloudflare/Akamai).
- Notification Service — Pushes activity alerts (new follower, mention) via Kafka-driven async workers.
Key Design Decisions:
- NoSQL for tweets and timelines — Cassandra or DynamoDB provides the write throughput and horizontal scalability that relational DBs cannot match at this volume.
- Redis for hot timelines — Top-N tweets per user cached as a sorted set; O(log n) inserts and range reads.
- Async everything non-critical — Fanout, media processing, and notifications are async via Kafka. Only the tweet write itself is synchronous.
- Sync vs. async communication — Timeline fetch and engagement submission are synchronous REST; fanout jobs and notification triggers are asynchronous event-driven.
Scaling Strategy: Shard Cassandra by user ID; partition Kafka topics by user ID for ordered fanout; use Kubernetes HPA on fanout workers; geo-replicate media via CDN edge nodes; rate-limit writes per user at the API Gateway to prevent abuse and write storms.
Case Study: Notification System
Design a general-purpose notification system that accepts events from upstream services (orders, auth, marketing) and reliably delivers messages to users across multiple channels — Email, SMS, Push, and In-App — while respecting user preferences, handling retries, and operating at 100M+ notifications per day.
Functional Requirements:
- Multi-source event ingestion — Accept events from any upstream service (order shipped, login alert, flash sale).
- Multi-channel delivery — Email (SendGrid/SES), SMS (Twilio), Push (Firebase FCM / APNs), In-App.
- User preference enforcement — Per-channel, per-event-type opt-in/opt-out, quiet hours, regional compliance (GDPR, DND).
- Template-based messages — Localized, parameterized templates; cache frequently used templates.
- Retry & dead-letter handling — At-least-once delivery with idempotency; failed messages routed to DLQ after N retries.
- Admin API — Trigger bulk campaigns, view delivery reports, debug failed deliveries.
Non-Functional Requirements:
- Scalability — 10M active users × 5 events/day × 2 channels = 100M notifications/day; 3× burst capacity.
- Reliability — At-least-once delivery guarantee; no duplicates on retry (idempotency keys).
- Low latency — Notifications delivered within seconds of the triggering event.
- Observability — Logs, metrics, and traceability for audits; Prometheus + Grafana + CloudWatch.
- Extensibility — Add new channels (WhatsApp, Slack) with minimal code change.
High-Level Architecture (Event Flow):
- Event Ingestor — Receives events from upstream services; applies rate-limiting and buffering via Kafka or SQS before processing.
- Notification Orchestrator — Determines which notifications to trigger based on event type and user preferences; coordinates with Preference Service and Template Service.
- Preference Service — Stores per-user channel and event-type preferences in PostgreSQL; cached in Redis for high-QPS reads.
- Template Service — Generates localized messages using Handlebars/Liquid templates; caches rendered output.
- Channel Queues + Workers — Dedicated Kafka topics and consumer worker pools per channel (Email, SMS, Push, In-App); each worker calls the appropriate provider API.
- Delivery Tracker / DLQ — Tracks delivery status per notification; routes undeliverable messages to a Dead Letter Queue for manual inspection or alternative delivery.
Key Design Decisions:
- Kafka for buffering — Absorbs event bursts (flash sales, outages) without overwhelming downstream workers; enables replay if a channel worker fails.
- Idempotency keys — Each notification event carries a unique ID; workers check before delivery to prevent duplicates on retry.
- Channel isolation — SMS workers are separate from Email workers; a Twilio outage doesn't block email delivery.
- Redis preference cache — Preference lookups happen on every notification; caching reduces DB load by orders of magnitude during spikes.
- Cost awareness — SMS is the most expensive channel; route to SMS only when Email or Push is unavailable or explicitly preferred.
Scaling Strategy: Horizontally scale channel workers independently (SMS workers may need fewer instances than email); use Kafka consumer groups for parallel processing; use KEDA (Kubernetes Event-Driven Autoscaling) to scale workers based on queue depth; store templates in S3 and cache aggressively; archive delivery logs in cold storage after 30 days.
Case Study: Rental Platform (Airbnb)
Design an online rental marketplace where hosts list short-term accommodations and guests search, book, and pay for stays — with real-time availability, calendar synchronization, media-heavy listings, and a global user base of millions.
Functional Requirements:
- Host capabilities — Create and update listings, upload photos/videos, set nightly pricing and availability via a calendar.
- Guest capabilities — Search listings with filters (location, dates, price, amenities), view details and reviews, book and pay.
- Availability calendar — Real-time sync with external calendars (Google Calendar, iCal) to prevent double-booking.
- Secure payments — Third-party gateway integration; host payouts after stay completion.
- Reviews & ratings — Post-stay reviews from guests; moderated by the platform.
- Notifications — Booking confirmations, cancellations, and reminders via email and push.
Non-Functional Requirements:
- High availability — 24/7 uptime, especially during peak travel seasons.
- Performance — Search response <300ms; geo-filtered results with Elasticsearch.
- Scalability — 5M DAU, 50M+ listings, 40–50M searches/day, 1M bookings/day.
- No double-booking — Atomic booking flow: availability lock → payment → confirmation.
- Localization — Multi-currency, multi-timezone, multi-language support.
Scale Estimates: 5M DAU, 100K concurrent users at peak; 50M+ listings (250 GB in PostgreSQL); 1M bookings/day (~1 TB/year); 500 TB of media storage (avg. 10 images × 1 MB × 50M listings); 1M payment transactions/day.
High-Level Architecture:
- API Gateway — Routes requests, enforces JWT-based auth, rate limits.
- User Service — Registration, login, profiles, email verification.
- Listing Service — Manages property details, amenities, pricing; backed by PostgreSQL with Elasticsearch for search.
- Search Service — Geo-based, filtered search using Elasticsearch; Redis caches hot search results.
- Availability Service — Manages booking calendars; handles external calendar sync via iCal/Google Calendar API; PostgreSQL for consistency.
- Booking Service — Orchestrates the 6-step booking flow: lock availability → draft reservation → initiate payment → verify → finalize → notify.
- Payment Service — Stripe/PayPal integration; webhook callbacks for async confirmation.
- Media Service — Chunked image/video upload to S3; CDN delivery (Cloudflare/Akamai).
- Calendar Sync Service — Bidirectional sync with external calendar providers; resolves conflicts.
- Kafka — Async event bus for booking events, notifications, and payment webhooks.
Key Design Decisions:
- Atomic booking flow — Availability lock, payment, and calendar update must succeed or all roll back. Use a saga pattern with compensating transactions for distributed rollback.
- Elasticsearch for search — Supports geo-distance queries, full-text amenity search, and range filters (price, dates) with sub-300ms response.
- Eventual consistency for availability — Minor delays in availability propagation are acceptable; use optimistic locking on the availability table to prevent double-booking.
- CDN for media — All listing images served from CDN edge nodes; reduces origin load by 90%+.
- RabbitMQ/Kafka for async flows — Booking events, payment confirmations, and notification dispatches are fully async to prevent blocking the critical booking path.
Scaling Strategy: Horizontally scale each microservice on Kubernetes; use Azure/AWS multi-region deployment with load balancers; replicate PostgreSQL across availability zones; partition Elasticsearch by listing geography; use Redis cluster for session and search caches; enable CDN caching for media with long TTLs.
Case Study: Auction Platform (eBay)
Design a real-time auction platform where sellers list items with a timed auction window, bidders compete with live bid updates, and the system determines a winner and processes payment when the auction closes — all with sub-second bid acknowledgment and fairness guarantees even during last-second bidding frenzies.
Functional Requirements:
- Item listing — Sellers set auction parameters: start time, end time, reserve price, buy-it-now price.
- Real-time bid placement — Bidders place bids; system validates (bid > current highest), stores, and broadcasts instantly.
- Live bid updates — All watchers of an auction see the new highest bid in real-time via WebSockets.
- Auction lifecycle — State machine: scheduled → active → ended; controlled by a scheduler service.
- Outbid notifications — Previous highest bidder notified immediately when outbid.
- Post-auction payment — Winning bidder receives payment request; seller notified upon completion.
Non-Functional Requirements:
- Sub-second bid latency — Bid acceptance and broadcast must complete in <500ms.
- Strong consistency for bids — No two bidders can simultaneously be the "highest bidder"; each bid must be serialized.
- High availability during peak — Popular auctions can have 10K concurrent watchers; system must not degrade.
- Fairness — Auction end time is enforced strictly; bids arriving after close are rejected.
- Security — Anti-bot protections; OAuth2 auth; PCI-compliant payment handling.
Scale Estimates: 5M registered users, 500K DAU; 1M ongoing auctions; 10M bids/day (~115/sec average); peak of 10K concurrent users on one popular auction; 100K payment transactions/day.
High-Level Architecture:
- API Gateway — Entry point for all requests; auth, rate limiting, routing.
- Auction Service — Manages auction lifecycle and state transitions (scheduled → active → ended).
- Bid Service — Validates and records bids with concurrency control; publishes bid events to Kafka.
- WebSocket Server — Clients subscribe to auction channels; Bid Service events fan out to all watchers in real-time.
- Scheduler Service — Queue-based (SQS delay / Redis TTL) scheduler that fires auction start/end events at precise times.
- Listing Service — Item metadata, categories, media.
- Payment Service — Post-auction payment initiation via Stripe/PayPal; retry logic with idempotency.
- Notification Service — Outbid alerts, auction-won messages, payment reminders.
- Analytics & Logging — Tracks bid trends, auction health, and fraud signals.
Key Design Decisions:
- Serialized bid processing — All bids for an auction pass through a single shard/partition in Kafka (keyed by auction ID), ensuring strict ordering without distributed locks.
- Optimistic locking on bid table — Each auction row carries a version number; a bid only succeeds if the version matches, preventing race conditions.
- WebSocket fan-out — Bid events published to Redis Pub/Sub; WebSocket gateway nodes subscribe and push to connected clients. Horizontal scaling: each WS server handles a subset of auction channels.
- Scheduler reliability — Auction end events are stored in Redis with TTL expiration AND backed by a delayed job queue (e.g., BullMQ, SQS). Both mechanisms must agree to close an auction, making the close idempotent.
- Async payment trigger — Payment is initiated asynchronously after auction close; a failure in payment does not roll back the auction result.
- Redis for hot auction state — Current highest bid and bidder ID cached in Redis for O(1) reads; DB is the source of truth but consulted only on write.
Scaling Strategy: Partition bid processing by auction ID across Kafka partitions; horizontally scale WebSocket servers behind a load balancer (sticky sessions or Redis Pub/Sub for cross-server fan-out); use PostgreSQL with row-level locking for bid writes; cache auction listings in Redis; auto-scale API and bid services on Kubernetes based on concurrent auction count.
Case Study: Cloud Storage (Dropbox / Google Drive)
Design a cloud file storage system where users can upload files of any type, sync them across all their devices in near real-time, share with collaborators, and access previous versions — at a scale of 10M active users, 5B files, and 10 petabytes of stored data.
Functional Requirements:
- File upload & download — Support files up to 5 GB; use chunked, resumable uploads.
- Multi-device sync — Changes on one device propagate to all other connected devices within 5 seconds.
- File organization — Folders, nested directories, tags.
- Sharing & collaboration — Public/private links, per-user access levels (view/edit), shared folders.
- File versioning — Maintain version history; allow rollback to any previous version.
- Soft delete & restore — Trash bin with restore option; permanent deletion after configurable period.
Non-Functional Requirements:
- Extreme durability — 99.999999999% ("eleven nines") — files must never be lost.
- Low latency — Fast uploads/downloads; metadata reads under 100ms.
- Sync latency <5 seconds — Event-driven push, not polling.
- Security — End-to-end encryption option; fine-grained ACL; TLS in transit.
- Cost efficiency — Deduplication and lifecycle policies to avoid storing identical data twice.
Scale Estimates: 10M users × 500 files average = 5B total files; average file size 2 MB → 10 PB total; 2K uploads/sec peak; 10K sync events/sec peak.
High-Level Architecture:
- Upload Service — Manages chunked/resumable uploads; initiates upload session, accepts chunk by chunk (PUT /upload/{id}/chunk), assembles on completion. Each chunk carries a checksum for integrity verification.
- Metadata Service — Stores file names, paths, ownership, permissions, version history; PostgreSQL for structured data, NoSQL for flexible attributes. Cached in Redis for high-QPS folder listings.
- Storage Service — Interfaces with cloud object storage (AWS S3 / Azure Blob / GCS); files stored as chunks with content-addressable naming (hash of content) enabling deduplication.
- Deduplication Service — Computes chunk fingerprints before upload; if a chunk already exists in the store, skip the upload and reference the existing chunk. Massive storage savings for common files.
- Sync Service — Pushes change events to connected devices via WebSocket or long-polling; uses Kafka for event fan-out across devices.
- Versioning Service — Tracks version metadata per file; stores deltas or full snapshots depending on file size; enables rollback via GET /files/{id}/version/{versionId}.
- Auth Service — OAuth2/JWT; validates permissions before every file operation.
Key Design Decisions:
- Chunked resumable uploads — Split files into 5 MB chunks; upload independently with checksum; resume from last successful chunk on failure. Eliminates the need to re-upload large files from scratch after a network interruption.
- Content-addressable storage — File chunks are named by their hash. Identical chunks (even across different users) are stored once. This is how Dropbox achieves dramatic storage efficiency.
- Metadata separate from content — Metadata DB (PostgreSQL) and blob storage (S3) are completely independent layers. Metadata reads are fast SQL queries; blob reads go directly to object storage or CDN.
- Event-driven sync — When a file changes, the Sync Service publishes a change event to Kafka; all connected devices for that user consume the event and pull the changed chunks. No polling required.
- CDN for shared links — Public/shared files are cached at CDN edge nodes; reduces origin egress costs and latency for recipients.
- SQL for structured, NoSQL for flexible metadata — User data, permissions, and file structure in PostgreSQL; chunk tracking and dynamic attributes in MongoDB for horizontal scalability.
Scaling Strategy: Shard the Metadata Service by user ID; replicate object storage across multiple availability zones (S3 handles this natively); scale Upload Service workers horizontally on Kubernetes; use Redis Cluster for metadata caching; implement lifecycle policies (move files to cold storage after 90 days of inactivity) to control cost at petabyte scale.
Case Study: Video Sharing Platform (YouTube)
Design a video sharing platform where 100M users upload 10M videos per day, 500M daily views are served globally, and each video must be transcoded into multiple resolutions and delivered via CDN with adaptive bitrate streaming — while keeping upload-to-playback latency low and storage costs manageable at petabyte scale.
Functional Requirements:
- Video upload — Chunked upload with progress tracking; supports files up to multi-GB.
- Video encoding — Transcode to multiple resolutions (240p, 480p, 720p, 1080p, 4K); generate thumbnails and HLS/DASH manifests for adaptive streaming.
- Video streaming — Adaptive bitrate streaming via CDN; quality adjusts to user bandwidth.
- Metadata & discovery — Title, description, tags, category; keyword and tag-based search.
- Engagement — Likes, comments, shares, view counts, channel subscriptions.
- Personalized feed — Home feed recommendations based on watch history and subscriptions.
Non-Functional Requirements:
- Massive storage — 500 TB/day raw uploads; ~1.5 PB/day after encoding to 4 variants; 45 PB/month.
- Global CDN delivery — 10 Tbps peak egress; 135 PB/day streaming bandwidth.
- Low streaming latency — First-frame time <2 seconds globally.
- High availability — Videos must be accessible even if encoding pipeline partially fails.
- Scalable search — Near-real-time indexing of 10M new videos/day.
High-Level Architecture:
- Upload & Ingestion Service — Accepts chunked video uploads; generates a video ID; stores raw file in temporary blob storage; enqueues an encoding job via Kafka/SQS.
- Encoding & Processing Pipeline — Consumes encoding jobs; transcodes to multiple resolutions using FFmpeg workers (GPU-accelerated); generates thumbnails and HLS/DASH manifest files; writes encoded chunks to final object storage.
- Video Storage + CDN — Encoded video chunks and manifests stored in S3/GCS; CDN (Cloudflare/CloudFront) caches at edge nodes worldwide. Players request manifests and fetch segments from the nearest CDN node.
- Metadata Service — Stores video title, tags, uploader, status, thumbnail URL; PostgreSQL for structured queries; Elasticsearch for full-text and tag search.
- User Service — Auth (OAuth2/JWT), channel management, subscription graph.
- Engagement Service — Async event logging for views, likes, comments; Redis counters for hot videos; aggregated periodically into analytics DB.
- Recommendation Engine — Consumes watch history events; generates personalized feed using collaborative filtering and content embeddings.
- Search & Discovery — Elasticsearch index updated in near-real-time as new videos are published.
Key Design Decisions:
- Encoding pipeline as async queue — Upload completes in seconds; encoding takes minutes. Decoupling via Kafka ensures the upload API never blocks on encoding. Status polled via GET /videos/{id}/status.
- HLS/DASH for adaptive streaming — Video is split into 6–10 second segments at each resolution; clients switch quality tiers based on available bandwidth — no buffering spikes.
- Multi-tier storage — Popular videos on hot storage (fast SSD-backed S3 tier); older/rare videos moved to cold storage (Glacier/Archive) automatically via lifecycle policies.
- Engagement counters in Redis — View and like counts are high-frequency writes; Redis INCR is O(1) and handles millions of increments/sec. Periodically flushed to the relational DB.
- Elasticsearch for search — New videos are indexed asynchronously via a Kafka consumer; supports tag, title, and category queries at scale without hitting the metadata DB directly.
- Abuse prevention — Rate limit upload API per user; scan encoded video for copyright fingerprints (Content ID) and explicit content before publishing.
Scaling Strategy: Auto-scale FFmpeg encoding workers on GPU instances (Kubernetes + GPU node pools); partition Kafka encoding topics by video ID; shard PostgreSQL metadata by video ID; geo-replicate CDN manifests for global low-latency delivery; use read replicas for metadata queries; implement CDN cache warming for new viral videos.
Case Study: Search Engine (Google Search)
Design a web-scale search engine that crawls billions of web pages, builds an inverted index for fast keyword lookup, ranks results by relevance using TF-IDF and PageRank, and responds to 50K queries per second with under 200ms end-to-end latency — while keeping the index fresh as the web changes.
Functional Requirements:
- Web crawling — Continuously discover and fetch public web pages at scale, respecting robots.txt and rate limits.
- Indexing — Parse HTML, extract and normalize text, build forward and inverted indexes.
- Keyword search — Accept user queries and return a ranked list of relevant documents.
- Ranking — Score results by TF-IDF relevance, PageRank (link authority), and content freshness.
- Re-indexing — Periodically re-crawl pages to pick up content changes; prioritize high-churn sites.
Non-Functional Requirements:
- Performance — <200ms total query response time (parsing <5ms, index lookup <20ms, ranking <50ms).
- Scalability — Index 100B+ pages; handle 50K QPS; storage budget of petabytes.
- Freshness — Index updated within hours of a page changing.
- Fault tolerance — No single point of failure; crawler retries on fetch failure; index replicated.
- Storage efficiency — Raw HTML ~10 PB; compressed inverted index ~500–800 GB for 100M pages.
Scale Estimates: 100M pages for MVP (100B for full scale); 10M active users; 50M queries/day; peak 1,000–2,000 QPS. Crawl target: 100M pages in 7 days → 170 pages/sec with 500 workers.
Core Components:
- URL Frontier & Scheduler — Priority queue of URLs to crawl; partitioned by domain hash; respects crawl politeness (rate limits per domain); uses SimHash fingerprinting to detect near-duplicate content.
- Crawler Workers — Distributed workers (500+) that fetch pages, store raw HTML in the Document Store, extract outbound links back into the Frontier, and report crawl status.
- Content Extractor & Parser — Strips HTML tags, tokenizes text, removes stop words, applies stemming; produces clean token lists per document.
- Indexer Service — Builds the forward index (docID → tokens + metadata) and the inverted index (token → list of docIDs with positions and frequencies). Indexes are partitioned and replicated across nodes.
- Document Store — Raw and parsed page content; stored in distributed file system (HDFS/S3) with columnar formats for compression.
- Query Service — Parses incoming query, looks up sharded inverted index, retrieves candidate document sets, scores and ranks them, returns top-N results. Backed by Redis cache for popular queries.
- Ranking Engine — Computes combined score: TF-IDF (term relevance) + PageRank (link authority) + freshness boost. Runs in-memory for speed.
Key Design Decisions:
- Sharded inverted index — Partitioned by term hash across many nodes; each shard handles a subset of the vocabulary. Queries fan out to all shards and results are merged and ranked centrally.
- Deduplication — SimHash fingerprinting detects near-duplicate pages before indexing, preventing the same content from appearing multiple times in results.
- Forward + inverted index separation — The inverted index enables fast term → document lookups; the forward index enables snippet generation and ranking feature extraction per document.
- Caching for hot queries — The top 1% of queries account for ~50% of traffic. Caching their results in Redis gives enormous throughput gains with minimal storage cost.
- Adaptive re-crawl scheduling — Pages that change frequently (news sites) are re-crawled every few hours; static pages are re-crawled weekly. Change-rate is tracked per domain.
- Kafka for pipeline coordination — Crawler → Parser → Indexer pipeline uses Kafka topics for decoupling; each stage processes independently and at its own rate.
Scaling Strategy: Horizontally scale crawler workers (partition by domain); shard inverted index by term hash with replication factor 3; cache top queries in Redis with TTL; use Elasticsearch as an alternative to custom inverted index for smaller-scale implementations; store raw content in S3 with lifecycle policies; use dedicated high-memory nodes for ranking engine.
Case Study: E-Commerce Marketplace (Amazon)
Design a multi-vendor e-commerce platform where buyers browse and purchase products, sellers manage inventory and listings, and the system handles secure checkout, payment processing, fraud detection, and order management — without ever overselling inventory, even during flash sales.
Functional Requirements:
- Product catalog — Sellers create and manage product listings with descriptions, images, categories, and pricing.
- Search & browse — Buyers search products by keyword, filter by category/price/rating; fast results expected.
- Shopping cart & checkout — Add to cart, update quantities, apply coupons, initiate checkout with inventory reservation.
- Inventory management — Real-time stock tracking; prevent overselling through atomic reservation at checkout.
- Secure payment — Integration with Stripe/Razorpay; PCI-DSS compliance; fraud detection alerts.
- Order management — Order lifecycle: placed → confirmed → shipped → delivered; seller and buyer notifications.
- Seller dashboard — Sales analytics, product management, payout tracking.
Non-Functional Requirements:
- Performance — Product search <300ms; cart/checkout <500ms.
- Scalability — 1M registered users at launch; elastic growth; 100 requests/sec peak during sales.
- Availability — 99.9% uptime; fault-tolerant catalog, cart, and checkout services.
- Consistency — Strong consistency for inventory updates; no overselling under any concurrency.
- Security — OAuth2/JWT auth; encrypted payment records; GDPR compliance for user data.
High-Level Architecture:
- API Gateway — Routes requests, enforces auth, rate limits per user/IP.
- User Service — Registration, login (OAuth2/JWT), profile management; PostgreSQL.
- Product Catalog Service — CRUD for products; PostgreSQL as source of truth; Elasticsearch index for fast keyword search; Redis cache for popular product pages.
- Inventory Service — Tracks stock levels per product; uses PostgreSQL with row-level locking for atomic reservations; Redis for fast stock-level reads.
- Order Management Service — Cart management, checkout orchestration, order lifecycle; PostgreSQL for transactional consistency.
- Payment Service — Initiates payment via Stripe/Razorpay; verifies via webhook; stores encrypted payment records; idempotency keys prevent duplicate charges.
- Notification Service — Email/SMS via Kafka-driven async workers; decoupled from the checkout critical path.
- Admin Service — Seller onboarding, product moderation, dispute resolution.
Key Design Decisions:
- Atomic inventory reservation — At checkout, the Inventory Service executes a database transaction that decrements stock only if stock > 0. Row-level locking in PostgreSQL ensures this is atomic even under concurrent requests.
- Elasticsearch for product search — Keyword search, faceted filtering (category, price range, rating), and typo tolerance are difficult to implement in PostgreSQL at scale. Elasticsearch is updated asynchronously when products change.
- Redis for hot data — Popular product pages, category listings, and session tokens cached in Redis; reduces PostgreSQL read load by 10–100× during traffic spikes.
- Event-driven async for non-critical flows — Order placement triggers Kafka events that drive inventory updates, email notifications, and seller alerts asynchronously. The buyer gets an order ID immediately; confirmation follows within seconds.
- Fraud detection — Rule-based (velocity checks, unusual amount patterns) at payment initiation; flag suspicious transactions for manual review rather than blocking (to avoid false positives hurting conversion).
- Saga pattern for checkout — Checkout spans multiple services (Inventory, Payment, Order). Use a saga with compensating transactions: if payment fails, release the inventory reservation and notify the buyer.
Scaling Strategy: Deploy each microservice independently on Kubernetes with HPA; shard PostgreSQL by user ID for user/order data and by product ID for catalog/inventory; use Elasticsearch cluster with multiple shards for product search; implement CDN for product images; scale Kafka consumer groups for notification workers based on queue depth; use managed cloud services (AWS RDS, ElastiCache, SQS) to reduce operational overhead.
Case Study: Collaborative Document Editor (Google Docs)
Design a web-based collaborative document editor where multiple users can edit the same document simultaneously, see each other's changes in real-time with under 100ms latency, and never see a conflicted or corrupted document state — backed by version history, access control, and autosave at a scale of 10M+ daily active users.
Functional Requirements:
- Create, edit, delete documents — Rich text editing; basic formatting (bold, italic, headings, lists).
- Real-time multi-user collaboration — 2–50 users editing simultaneously; changes visible to all collaborators instantly.
- Conflict-free document state — No matter the order operations arrive, all clients converge to the same document.
- Version history & change tracking — Full operation log; ability to revert to any prior state.
- Access control — Owner can grant view or edit access; shareable links with configurable permissions.
- Autosave — Periodic persistence to durable storage; crash recovery on reconnect.
Non-Functional Requirements:
- Ultra-low latency — Real-time sync with <100ms latency for collaborators in the same region.
- Scalability — 10M+ DAU; 200K+ concurrent editors during peak; ~10B operations/day.
- High availability — 99.9% uptime; seamless reconnect after network loss; partial-operation recovery.
- Consistency — All clients must converge to the same document state even with out-of-order or concurrent operations.
- Security — TLS in transit; AES encryption at rest; OAuth2/JWT auth; permission checks on every operation.
The Core Challenge — Conflict Resolution: When Alice inserts "Hello" at position 5 and Bob simultaneously deletes the character at position 3, the naive approach (last-write-wins) produces a corrupted document. Two algorithmic approaches solve this:
- Operational Transformation (OT) — Each operation is transformed against concurrent operations before being applied, so the end result is the same on all clients regardless of arrival order. Used by Google Docs historically. CPU-intensive but well-understood.
- CRDT (Conflict-Free Replicated Data Types) — Operations are designed so they commute — applying them in any order always yields the same result. Libraries like Yjs and Automerge implement this. More scalable and works offline; increasingly preferred.
High-Level Architecture:
- Client (browser/mobile) — Rich text editor (e.g., Quill, ProseMirror); maintains a local copy of the document; sends operations over WebSocket; applies incoming remote operations using OT/CRDT.
- API Gateway — Auth, routing, rate limiting for REST and WebSocket connections.
- Collab Service (Real-Time Sync) — Maintains a WebSocket connection per active document session; applies OT/CRDT transforms to incoming operations; broadcasts transformed operations to all collaborators on the document.
- Document Service — REST API for creating, loading, and saving documents; manages metadata and permissions; backed by PostgreSQL (metadata) and MongoDB (document content).
- Versioning Service — Stores operation log per document; takes snapshots every N operations to enable fast document reconstruction without replaying thousands of ops.
- Storage Layer — Hot storage (Redis for active document state), warm storage (PostgreSQL/MongoDB for recent documents), cold storage (S3 for archived documents and version history).
- Kafka (Messaging Layer) — Event fan-out for sync, async persistence, and audit logging. Collab Service publishes operation events; Versioning Service and Document Service consume asynchronously.
Real-Time Sync Flow:
- Step 1 — User types; client generates an operation (insert/delete at position).
- Step 2 — Operation sent over WebSocket to Collab Service with a document version number.
- Step 3 — Collab Service applies OT/CRDT transform against any concurrent operations; appends to operation log.
- Step 4 — Transformed operation broadcast to all other clients on the document.
- Step 5 — Clients apply the operation locally; all clients converge to the same state.
- Step 6 — Autosave: every 30 seconds (or every N operations), a snapshot is persisted to the Document Service.
Key Design Decisions:
- WebSocket for real-time communication — Persistent bidirectional connection eliminates the overhead of repeated HTTP handshakes; essential for <100ms perceived latency.
- Stateful Collab Service — Each document session is pinned to one Collab Service instance (sticky routing). If that instance fails, clients reconnect and re-sync from the latest snapshot + operation log.
- Snapshot + delta model — Storing full document snapshots every 100 operations means reconstruction requires at most 100 operation replays, not thousands. Balances storage cost against recovery speed.
- gRPC for internal services — Document Service, Versioning Service, and Collab Service communicate via gRPC for low-latency, strongly-typed internal calls.
- Offline support — CRDT-based clients can accept edits offline and merge them when reconnected without conflicts, enabling offline editing that syncs seamlessly.
- Redis for hot document state — Active document's current content and operation queue kept in Redis for sub-millisecond access by the Collab Service; flushed to persistent store on close.
Scaling Strategy: Route documents to Collab Service instances by document ID (consistent hashing); scale Collab Service horizontally with Redis Pub/Sub for cross-instance operation fan-out on shared documents; use Kubernetes HPA based on active WebSocket connections; partition Kafka topics by document ID for ordered operation processing; archive version history to S3 after 90 days; use PostgreSQL read replicas for metadata queries.
Further Reading
Final Tips
- Draw first, then explain. A diagram on the board is shared memory; words evaporate.
- Ask clarifying questions for five full minutes. Jumping straight to Kafka is the #1 red flag.
- State assumptions numerically. "Assuming 10M DAU and 100:1 reads…" makes everything after it defensible.
- Think out loud. A wrong path reasoned clearly scores higher than a right answer delivered silently.
- Name every trade-off. "Cache gives 10× reads at the cost of ≤5 min staleness" — that sentence shape, constantly.
- Start simple, scale on demand. Monolith → replicas → cache → shard → queue. Each step justified by a number.
- Close with self-critique. "If I had more time, I'd address hot shards by routing the top 1% of user IDs to a dedicated fast path." Shows you know what's missing.
You now have the whole toolbox: scale math (01), CAP instincts (02), consistency patterns (03), DNS & CDNs (04), load balancing (05), caching (06), databases (07), queues (08), protocols (09), microservices (10), and security (11). System design isn't memorization — it's knowing which trade-off to reach for. Go build something beautiful.
Final Boss Question
Interviewer: "Design Instagram." What's the strongest possible first move?
Interactive: Back-of-Envelope Estimator
The first thing interviewers want to see is napkin math. Set your DAU, requests per user, and response size — the estimator computes RPS, bandwidth, storage, and server count in real time.