System Design Interview: Fan-out OOM & Duplicate Tweets
Fan-out worker OOM caused duplicate timeline entries and 12-min Kafka lag.
20+ years shipping production code across the stack, with years spent interviewing engineers. Lessons pulled from things that broke in production.
- ✓Deep production experience
- ✓Understanding of internals and trade-offs
- ✓Experience debugging complex systems
- System design interviews test structured ambiguity navigation, not knowledge recall — process matters more than the final diagram
- Four phases: clarify requirements (5 min), high-level design (15 min), deep dive (20 min), trade-offs and close (5 min)
- Never draw a component until you have 3 specific NFRs with real numbers: QPS, latency SLA, availability target
- Hybrid fan-out (push for normal users, pull for celebrities) is the production answer to social feed scaling
- Proactively surface your own design weaknesses before the interviewer finds them — this is the highest-signal senior behavior
- Biggest mistake: jumping to Kafka in the first 60 seconds without numbers to justify it — interviewers read this as guessing
Imagine someone walks up to you and says: 'Design a city.' You wouldn't just start drawing roads randomly — you'd ask how many people live there, what they need, and what the budget is. System design interviews work exactly the same way. The interviewer hands you a blank whiteboard and says 'design YouTube' — and what they're actually testing is whether you can think like an architect before you start laying bricks. The framework in this article is your blueprint for that conversation.
| Chrome | Firefox | Safari | Edge |
|---|---|---|---|
| ✓ | ✓ | ✓ | ✓ |
System design interviews are the highest-signal round in any senior engineering hiring process. They separate engineers who can implement from engineers who can think at scale. A candidate who writes flawless LeetCode solutions can still completely bomb a system design round because the skills are fundamentally different — one is puzzle-solving, the other is structured ambiguity navigation.
The problem isn't that engineers don't know distributed systems. Most senior candidates have read about consistent hashing, Kafka, and sharding. The problem is they don't know how to structure a 45-minute conversation into a coherent narrative that demonstrates both technical depth and product instinct simultaneously.
By the end of this article you'll have a repeatable, time-boxed framework you can apply to any system design prompt. You'll know exactly what to say in each phase, which trade-offs to surface proactively, how to handle follow-up pressure, and what signals separate a 'strong hire' from a 'hire' at FAANG-level bars.
Why Fan-out OOM & Duplicate Tweets Are the Real System Design Test
System design interviews evaluate your ability to build scalable, fault-tolerant distributed systems under realistic constraints. The core mechanic is not memorizing architectures but reasoning through trade-offs: consistency vs. availability, latency vs. throughput, and cost vs. reliability. For fan-out, you must decide between push (write-time fan-out) and pull (read-time fan-out), each with distinct memory and latency profiles. Duplicate tweets expose idempotency failures — a missing dedup key or retry logic can double-write to timelines, corrupting user experience and downstream analytics.
In practice, fan-out OOM occurs when a celebrity tweet triggers a push to millions of followers, overwhelming the fan-out service’s heap. The key property is that fan-out size is O(followers) per tweet, not O(1). Without backpressure or batching, a single hot tweet can exhaust memory in seconds. Duplicate tweets arise from at-least-once delivery semantics in message queues: a producer retry after a timeout may succeed twice, and if the consumer lacks idempotency (e.g., a unique tweet ID check before insert), duplicates persist.
Use push fan-out for high-read, low-write systems (e.g., Twitter timelines) where read latency must be sub-100ms. Use pull fan-out for sparse followers or when write throughput dominates. Idempotency keys (tweet ID + timestamp) are non-negotiable for any system with retries. These decisions matter because a single design flaw — like unbounded fan-out or missing dedup — can cascade into global outages, as seen in real Twitter and Facebook incidents.
Phase 1 — Clarify Requirements Before Touching the Whiteboard (Minutes 0–5)
The single biggest mistake candidates make is starting to design immediately. Interviewers deliberately leave the prompt vague because the ability to identify the right questions is itself a senior engineering skill. 'Design Twitter' could mean the timeline, the search, the DM system, or the ad platform — they're completely different problems with completely different bottlenecks.
Spend the first five minutes asking structured clarifying questions in two buckets: functional requirements and non-functional requirements.
Functional requirements define what the system does. Which features are in scope? 'Design Twitter' — are we doing tweet posting, home timeline, follower graph, search, or all of them? Pin down one or two core flows and explicitly park the rest: 'I'll focus on posting tweets and reading the home timeline. I'll note search and DMs as out of scope unless we have time.'
Non-functional requirements define how the system behaves under load. This is where most candidates are too vague. Don't ask 'how many users?' — ask specifically: 'Are we targeting Twitter's actual scale of ~500M daily active users, or is this a startup at 1M DAU?' Then drive the numbers yourself. Assume a write:read ratio, calculate QPS, estimate storage needs. Saying 'let's assume 500M DAU, 100M tweets per day, roughly 1,200 tweet writes per second and maybe 10x that for reads — so about 12,000 timeline reads per second as a floor, likely higher with fan-out' shows the interviewer you can reason from first principles rather than recite memorized numbers.
Also clarify consistency requirements explicitly. Is eventual consistency acceptable for the timeline? Almost certainly yes — a user seeing a tweet 2 seconds late is tolerable. For payments or inventory? Never. Lock in the consistency model you're designing to before you propose any components, because it determines whether you can use async fan-out at all.
Phase 2 — High-Level Design and the API Contract (Minutes 5–20)
Once requirements are locked, draw the 30,000-foot view before zooming in anywhere. Most candidates zoom in too fast — they start talking about database sharding before they've even established what services exist. Interviewers want to see that you can hold the whole system in your head before optimizing any part of it.
Start with the client-to-server path. Draw: client → load balancer → API gateway → application services → data stores. Then define your API contract for the core flows. This is non-negotiable for senior roles — defining the API before designing the backend proves you think contract-first, which is how production systems are actually built. Teams agree on the API surface first, then build services independently against that contract.
For tweet posting: POST /v1/tweets, body: {user_id, content, media_ids[]}, response: {tweet_id, created_at}. For timeline: GET /v1/users/{user_id}/timeline?cursor={cursor}&limit=20, response: {tweets: [...], next_cursor: string}. Note the cursor-based pagination — explain why explicitly: offset pagination requires the database to scan and discard N rows to reach the offset, which degrades to a full table scan at depth. Cursor pagination hits the index directly.
Decompose into services early. For Twitter, you need at minimum: a Tweet Service (writes and reads), a Timeline Service (fan-out and reads), a User Service (auth and profiles), and a Media Service (upload/serve images). Keep services small and name them after the business capability, not the technology. Don't say 'the Java microservice' — say 'the Timeline Service.' This signals domain-driven thinking and maps directly to how you'd structure teams around the system.
For each service, identify the primary data it owns. The Tweet Service owns a tweets table. The Timeline Service owns precomputed timeline caches. The User Service owns the social graph. Call out the anti-pattern explicitly before the interviewer asks about it: 'I want to be clear that each service owns its own datastore. The Timeline Service does not query the Tweet Service's database directly. Cross-service communication happens via APIs or async events on a message queue. Shared databases create hidden coupling that makes services impossible to scale or deploy independently — I've seen this pattern cause production outages where a slow query from one service brought down an unrelated service sharing the same DB connection pool.'
Phase 3 — Deep Dive on the Hard Problems (Minutes 20–40)
This is where the interview is won or lost. After the high-level design, a good interviewer will steer you toward the hardest sub-problem in your design. For Twitter, that's the fan-out problem: when Katy Perry tweets, how do you push that tweet to 150M followers without your system catching fire?
There are two approaches — fan-out on write (push) and fan-out on read (pull) — and neither is universally correct. This is a classic system design trade-off that interviewers use specifically because it has no single right answer. The right choice depends on the read:write ratio and the distribution of follower counts in your user base.
Fan-out on write: when a tweet is posted, the Tweet Service publishes an event to a fan-out queue. A fleet of fan-out workers picks up the event and writes the tweet_id into each follower's precomputed timeline in Redis. Timeline reads are then a simple Redis ZSET lookup — sub-millisecond. The cost? Writing one tweet for a celebrity with 50M followers means 50M Redis writes. This is write amplification at its most extreme. At 3,600 writes per second at peak, and assuming the average user has 1,000 followers, that's 3.6M fan-out writes per second for average users alone — before you account for any celebrity traffic.
Fan-out on read: when a user opens their timeline, the Timeline Service queries the social graph to get their followed accounts, fetches recent tweets from each of those accounts, merges and sorts them, and returns the result. No write amplification — but read time scales linearly with the number of followed accounts. Following 5,000 accounts means up to 5,000 DB lookups on every timeline refresh, which violates the 200ms p99 SLA we set in Phase 1.
The production answer, used by Twitter and Instagram, is a hybrid: fan-out on write for normal users (fewer than 10K followers), fan-out on read for celebrities (more than 10K followers). When Katy Perry's tweet appears in your timeline, it was lazily fetched at read time and merged in memory with your precomputed timeline from non-celebrity follows. This caps write amplification at a manageable level while keeping reads fast for the overwhelming majority of cases. The 10K threshold is a tunable configuration value, not a magic number — you'd calibrate it against your actual follower distribution and Redis write capacity.
Phase 4 — Trade-offs, Bottlenecks, and How to Close Strong (Minutes 40–45)
The last five minutes are your chance to demonstrate that you think about systems holistically — not just 'does it work?' but 'how does it fail, and how do we recover?' Strong candidates proactively surface the weaknesses in their own design before the interviewer has to find them. This is not just a performance trick — it reflects genuine production experience, because engineers who've actually operated systems at scale know that the interesting questions are always about the failure modes, not the happy path.
Walk through your design and call out at least three potential bottlenecks with mitigations. Don't wait to be asked. For our Twitter design: (1) the fan-out queue — if Kafka falls behind during a traffic spike, timeline freshness degrades. Mitigation: monitor consumer lag per partition, auto-scale fan-out workers on lag metric, implement a dead-letter queue for failed fan-out events, and fall back to full fan-out-on-read as a degraded mode if lag exceeds five minutes. (2) Redis memory — precomputing timelines for 500M users at ~800 tweet_ids each, stored as 8-byte integers, is roughly 3.2TB of Redis storage. Manageable with a Redis cluster, but requires LRU eviction, timeline key expiry for inactive users, and strict enforcement that only tweet_ids (not full objects) are stored in Redis. (3) The single-region failure mode — the design as described has no geographic failover. For 99.99% global availability, deploy identical stacks in three regions with latency-based DNS routing, and replicate tweets asynchronously across regions via Kafka MirrorMaker 2, accepting ~500ms of cross-region replication lag.
Then close with a design summary — this is almost universally skipped by candidates but it's one of the most powerful things you can do: 'To summarize — we designed a Twitter clone handling 1,200 tweet writes and 120,000 timeline reads per second. The key architectural insight was the hybrid fan-out strategy that caps write amplification while keeping read latency under 200ms p99. The main trade-off was operational complexity — the fan-out worker requires careful idempotency handling and batch checkpointing that would not exist in a simpler pull-only design.' One paragraph. Architecture recapped. Trade-off named. This demonstrates that you can communicate to a non-technical stakeholder or an engineering manager, not just to the person next to you at a whiteboard.
Finally, leave time to ask the interviewer a genuine question about their actual systems. 'How does your team handle the fan-out problem today — did you go hybrid, or take a different approach?' This signals collaborative instinct and intellectual curiosity. Interviewers remember candidates who made the conversation feel like a peer discussion rather than an oral exam.
Phase 0: Capacity Estimation — Stop Designing Blind
You don't build a bridge by guessing how many cars cross it. Yet most candidates jump into designing distributed systems without a single back-of-the-envelope calculation. That's how you end up with a 50-node Cassandra cluster for a service that gets 200 requests per day.
Capacity estimation isn't busywork. It forces you to surface hard constraints before they become production incidents. Start with traffic: read-heavy or write-heavy? A typical Twitter clone might see 80% reads, 20% writes. Take the monthly active users (say 100M), daily active ratio (50%), and average requests per user per day (cached newsfeed refreshes on open). That's roughly 50M users doing 200 requests/day = 10 billion daily requests. Peak is 5x that. Now you know your API gateway needs to handle 115k requests/second without sweating.
Storage follows: each tweet payload is ~700 bytes after metadata. One year of all tweets at 100M/day is 25 TB. No big deal. But the media pipeline? Each image averages 500 KB. Now you're looking at 50 TB per day. Suddenly object storage isn't optional, it's mandatory. Network bandwidth calc tells you: don't even think about moving 50 TB/day through a single 1 Gbps link. That's 466 Mbps sustained, which leaves zero headroom for replication. You need a 10 Gbps inter-connect _or_ a CDN.
The number isn't the point. The clarity is. Write these estimates on the whiteboard before drawing a single box.
Phase 5: Database Schema — Where the Interview Happens
You've drawn the boxes and arrows. Now the interviewer says: 'How do you store it?' This is where 70% of candidates fold. They wave their hands at a tinder box labeled 'database' and move on. Don't be that person. The schema reveals whether you understand read patterns, write patterns, and consistency trade-offs.
For a Twitter timeline system, you have three core tables: users, tweets, and timeline_cache. The users table is standard SQL — UUID, handle, profile metadata. Tweets need careful indexing. A global tweet_id as primary key (use Snowflake IDs, not auto-increment) and a secondary index on author_id sorted by created_at descending for the user's own timeline. That's your hot path query.
The evil bit is the home timeline. You _don't_ query the tweet table for 800 followees on every refresh. That's a full table scan per request. Instead, build a timeline_cache: a Redis sorted set per user with tweet IDs and scores (timestamps). Write to it on tweet creation via fanout, capped at 800 entries. If the user misses a write, they see stale data for 5 seconds. That's acceptable. Strong consistency betrays you.
Now the DB selection: use Postgres for relational integrity (users, relationships). Use Cassandra for write-heavy tweet ingestion at massive scale. Use Redis for in-memory timeline materialization. Three databases with clear ownership. No silver bullet. Each has a job, and you know why.
AI Components in System Design: Embeddings, Vector DBs, RAG
Modern system design interviews increasingly test candidates on AI integration. For a Twitter-like feed, embeddings can power personalized recommendations, search, and duplicate detection. Embeddings convert tweets into dense vectors capturing semantic meaning. Use a pre-trained model like BERT or Sentence-BERT to generate 768-dimensional vectors for each tweet. Store these in a vector database (e.g., Pinecone, Weaviate, or pgvector) for efficient similarity search. For duplicate tweet detection, compute cosine similarity between incoming tweet embeddings and recent tweets; if similarity exceeds a threshold (e.g., 0.95), flag as duplicate. This complements traditional exact-match dedup. For feed ranking, use Retrieval-Augmented Generation (RAG): retrieve top-K relevant tweets based on user embedding (derived from their history) and feed them to a ranking model. Example: user embedding = average of last 50 liked tweet embeddings. RAG pipeline: (1) embed user query/user vector, (2) query vector DB for nearest neighbors, (3) pass results to a lightweight ML model for final ranking. This reduces latency vs. full neural ranking. Trade-offs: vector DB adds operational complexity and cost; embedding generation requires GPU inference at scale. Use approximate nearest neighbor (ANN) indexes (e.g., HNSW) for sub-100ms queries. In an interview, discuss trade-offs: accuracy vs. latency, cold start for new users, and incremental embedding updates via CDC.
Event-Driven Architecture: Kafka, Debezium, CDC Patterns
Fan-out OOM and duplicate tweets are exacerbated by synchronous fan-out. An event-driven architecture decouples tweet ingestion from fan-out. Use Apache Kafka as the central event bus. When a user posts a tweet, the API gateway publishes a 'TweetCreated' event to Kafka. Multiple consumers process this event asynchronously: (1) Fan-out service: reads the event and writes the tweet to followers' timelines (using a batch write to a distributed cache like Redis). (2) Duplicate detection service: checks for duplicates via exact match (tweet ID) and semantic match (embedding similarity). (3) Notification service: sends push notifications. Debezium, a CDC (Change Data Capture) tool, can stream changes from the primary database (e.g., PostgreSQL) to Kafka without dual writes. For example, when a tweet is inserted into the 'tweets' table, Debezium captures the change and publishes it to a Kafka topic. This ensures exactly-once semantics and reduces application complexity. For fan-out, use Kafka partitions keyed by follower user ID to parallelize writes. To avoid OOM, the fan-out service processes events in batches (e.g., 100 events per batch) and uses backpressure (Kafka consumer pause/resume). For duplicate tweets, the duplicate detection service can use a sliding window cache (e.g., Redis with TTL) of recent tweet IDs and embeddings. CDC also enables rebuilding the timeline cache from scratch by replaying Kafka topics. Trade-offs: Kafka adds latency (milliseconds) and operational overhead; CDC requires careful schema management. In an interview, discuss how to handle failures: dead letter queues for failed events, idempotent consumers, and exactly-once processing with Kafka transactions.
Observability-Driven Design: Tracing, Metrics, Logging Strategy
System design interviews often overlook observability, but it's critical for debugging fan-out OOM and duplicate tweets. Design for observability from the start. Use distributed tracing (e.g., OpenTelemetry) to trace a tweet's lifecycle: from API gateway → Kafka producer → fan-out consumer → Redis write. Each span captures latency and errors. For example, if a fan-out consumer OOMs, tracing shows the exact step where memory spiked. Metrics: (1) Fan-out latency p50/p99, (2) Kafka consumer lag, (3) Redis memory usage per timeline, (4) duplicate detection rate (false positives/negatives). Use Prometheus to collect metrics and Grafana for dashboards. Logging: structured logs (JSON) with correlation IDs. For duplicate tweets, log the similarity score and threshold. Set up alerts: if fan-out latency > 500ms or consumer lag > 1000, page on-call. For OOM prevention, monitor heap usage and set a high-water mark to pause consumers. Example: a Go fan-out service uses pprof to detect memory leaks. In an interview, discuss how observability helps during the deep dive: e.g., tracing reveals that duplicate detection is slow due to embedding generation, prompting a switch to a faster model. Also discuss cost: tracing adds overhead (sampling rate 1% for high throughput). Production insight: Twitter uses Zipkin for tracing and Observability as a Service (e.g., Datadog) for unified dashboards. Key takeaway: Observability-driven design ensures you can detect and fix fan-out OOM and duplicate tweet issues in production.
Fan-out Worker Crash on Celebrity Tweet Causes Duplicate Timeline Entries and OOM
- Never fetch unbounded lists in a single call — always use cursor pagination with a batch size limit.
- At-least-once delivery requires idempotent writes — test your idempotency guarantee by simulating a crash-and-replay scenario in staging before shipping.
- Score encoding precision matters for ZSET idempotency — normalize to a consistent unit before writing and enforce it in a shared utility function so no caller can deviate.
- Checkpoint batch progress externally (Redis or DB) so workers can resume, not restart, after a crash — the difference between resuming at offset 30,001 and replaying from zero is the difference between a 2-minute recovery and a 90-minute incident.
kafka-consumer-groups.sh --bootstrap-server localhost:9092 --describe --group fan-out-workerskafka-consumer-groups.sh --bootstrap-server localhost:9092 --describe --group fan-out-workers | awk '$5 > 0 {print $1, $2, $5}'| File | Command / Code | Purpose |
|---|---|---|
| io | "Before I start designing, I want to make sure we're aligned on scope. | Phase 1 |
| io | API DESIGN — Tweet Posting Flow: | Phase 2 |
| io | When a tweet is posted: | Phase 3 |
| io | "Let me stress-test my own design before we wrap up." | Phase 4 |
| CapacityPlanner.py | MAU = 100_000_000 # monthly active users | Phase 0: Capacity Estimation |
| TimelineSchema.py | CREATE TABLE users ( | Phase 5: Database Schema |
| duplicate_detection.py | from sentence_transformers import SentenceTransformer | AI Components in System Design |
| fanout_consumer.py | from kafka import KafkaConsumer | Event-Driven Architecture |
| tracing_setup.py | from opentelemetry import trace | Observability-Driven Design |
Key takeaways
Interview Questions on This Topic
You've designed a fan-out system using Kafka and Redis. A fan-out worker crashes after processing 30,000 of a celebrity's 50M followers. When it restarts and replays the Kafka message, how do you ensure those 30,000 followers don't get duplicate timeline entries?
Frequently Asked Questions
20+ years shipping production code across the stack, with years spent interviewing engineers. Lessons pulled from things that broke in production.
That's System Design Interview. Mark it forged?
12 min read · try the examples if you haven't