News Feed System#
Problem statement (interviewer prompt)
Design a Twitter/Facebook-style home feed for 1B users with avg 200 followees. Show the latest posts from people they follow, ranked by relevance. Handle the celebrity fan-out problem and keep feed-open p99 under 200ms.
flowchart LR
POST([User posts])
FAN[[Fan-out service]]
TLC[(Per-user Timeline Cache<br/>Redis lists)]
GET[Feed API]
RANK([Ranker])
U([Reader])
POST --> FAN
FAN -->|push to followers| TLC
U --> GET --> TLC
TLC --> RANK --> GET --> U
classDef client fill:#dbeafe,stroke:#1e40af,stroke-width:1px,color:#0f172a;
classDef edge fill:#cffafe,stroke:#0e7490,stroke-width:1px,color:#0f172a;
classDef service fill:#fef3c7,stroke:#92400e,stroke-width:1px,color:#0f172a;
classDef datastore fill:#fee2e2,stroke:#991b1b,stroke-width:1px,color:#0f172a;
classDef cache fill:#fed7aa,stroke:#9a3412,stroke-width:1px,color:#0f172a;
classDef queue fill:#ede9fe,stroke:#5b21b6,stroke-width:1px,color:#0f172a;
classDef compute fill:#d1fae5,stroke:#065f46,stroke-width:1px,color:#0f172a;
classDef storage fill:#e5e7eb,stroke:#374151,stroke-width:1px,color:#0f172a;
classDef external fill:#fce7f3,stroke:#9d174d,stroke-width:1px,color:#0f172a;
classDef obs fill:#f3e8ff,stroke:#6b21a8,stroke-width:1px,color:#0f172a;
class POST,U client;
class GET service;
class TLC cache;
class FAN queue;
class RANK compute;
flowchart TB
subgraph Write[Write Path]
POST[Compose / Post API]
MQ[[Kafka post events]]
FAN[[Fan-out workers]]
FG[Follower graph<br/>cached]
HOT([Hot-user detector<br/>celebrity threshold])
end
subgraph Stores
POSTS[(Posts DB<br/>Cassandra: post_id PK)]
GRAPH[(Social Graph<br/>FlockDB / TAO)]
TL[(Timeline cache<br/>Redis ZSET per user)]
MEDIA[(Object store + CDN)]
end
subgraph Read[Read Path]
FEED[Feed API]
MERGE([Merger<br/>cached TL + pull from celebs])
RANK[Ranking model<br/>ML scoring]
HYD([Hydrator<br/>fetch post + author + media])
PAG[Cursor pagination]
end
subgraph ML[Ranking & Signals]
SIG[Signals<br/>recency, affinity, engagement]
MOD([Ranker - GBDT / DNN])
EXP[Experiments / A-B]
end
subgraph Ops
DEDUP([Dedup<br/>seen posts per user])
SHIELD[Spam / safety filter]
NOTIF([Notification trigger])
OBS[Metrics / SLO]
end
USER[Reader] --> FEED --> MERGE
TL --> MERGE
POSTS --> HYD
GRAPH --> MERGE
MERGE --> RANK --> HYD --> PAG --> USER
POSTER[Author] --> POST --> MQ --> FAN
FG -.->|fan-out push| TL
HOT -.->|skip push| MQ
HOT -.->|read-time pull| MERGE
POSTS --- POST
MEDIA --- POST
SIG --> MOD --> RANK
EXP --> RANK
DEDUP --> PAG
SHIELD --> RANK
NOTIF --- MQ
OBS --- FEED
classDef client fill:#dbeafe,stroke:#1e40af,stroke-width:1px,color:#0f172a;
classDef edge fill:#cffafe,stroke:#0e7490,stroke-width:1px,color:#0f172a;
classDef service fill:#fef3c7,stroke:#92400e,stroke-width:1px,color:#0f172a;
classDef datastore fill:#fee2e2,stroke:#991b1b,stroke-width:1px,color:#0f172a;
classDef cache fill:#fed7aa,stroke:#9a3412,stroke-width:1px,color:#0f172a;
classDef queue fill:#ede9fe,stroke:#5b21b6,stroke-width:1px,color:#0f172a;
classDef compute fill:#d1fae5,stroke:#065f46,stroke-width:1px,color:#0f172a;
classDef storage fill:#e5e7eb,stroke:#374151,stroke-width:1px,color:#0f172a;
classDef external fill:#fce7f3,stroke:#9d174d,stroke-width:1px,color:#0f172a;
classDef obs fill:#f3e8ff,stroke:#6b21a8,stroke-width:1px,color:#0f172a;
class HOT,DEDUP,USER client;
class POST,FG,FEED,RANK,PAG,SIG,EXP,SHIELD,POSTER service;
class POSTS datastore;
class TL cache;
class MQ,FAN queue;
class MERGE,HYD,MOD,NOTIF compute;
class MEDIA storage;
class OBS obs;
Strategies#
- Push (fan-out-on-write): when author posts, write to each follower's timeline list. Cheap reads, expensive for celebs.
- Pull (fan-out-on-read): at read time, query latest posts from each followee. Cheap writes, expensive for big followee sets.
- Hybrid (most prod systems): push for normal users; for celeb authors, skip push and pull at read.
Timeline cache layout#
- Redis ZSET per user:
score=created_ts, member=post_id, capped at e.g. 1k entries (LTRIM). - Cold readers: rebuild from Posts DB on demand.
Ranking#
- Features: post age, author affinity, prior engagement, content quality, freshness.
- Online ranker (low-latency) on top of cached candidates.
- Personalization via embeddings + ANN candidate gen (for explore).
Edge cases#
- Celebrity 100M+ followers - fan-out IO storm. Treat as pull-only.
- Multi-DC: writes regional, async replicate timelines + posts.
- Deletes / edits: tombstones + read-time filter; eventually rewritten.
Glossary & fundamentals#
Concepts referenced in this design. Each row links to its canonical page; the tag column shows whether it is a high-level (HLD) or low-level (LLD) concept.
| Tag | Concept | What it is | Page |
|---|---|---|---|
HLD |
CDN | edge caching for static assets | cdn |
HLD |
Pub/Sub & message brokers | topics, consumer groups, delivery semantics | pub-sub-pattern |
HLD |
Leader/follower replication | sync/semi-sync/async replication, failover | replication-leader-follower |
HLD |
Observability | metrics, logs, traces, SLOs | observability |
HLD |
Search internals | inverted index, BM25, embeddings, ANN | search-internals |
LLD |
REST API design | verbs, statuses, pagination, errors | rest-api-design |
Quick reference#
Functional#
- User can post text/media.
- Follow graph (asymmetric).
- Each user gets ordered feed of followees' posts.
- Pagination, freshness, ranking, hide/mute.
Non-functional#
- p99 feed open < 200 ms.
- Hot-celeb posts visible in seconds.
- 1B users, avg 200 followees, avg 10 posts/day.
Capacity#
- Posts/day: 1B × 10 ÷ 5 (5% post) = 2B posts/day → 23k/s avg.
- Fan-out writes: 2B × 200 (avg followers) = 400B/day = 4.6M/s.
- Storage: 2B × 1 KB × 365 d = 730 TB/yr.
- Timeline cache: 1B users × 1k posts × 16 B ≈ 16 TB (Redis cluster).
API#
Trade-offs#
- Push great for normal users; useless for Justin Bieber.
- Pull great for celebs; bad for normal users (scatter).
- Hybrid is universal; complexity in merge step.
- ML ranking boosts engagement but adds compute + ethics surface.
- Strong vs eventual ordering - usually eventual is fine; users tolerate seconds.
Refs#
- Twitter timeline blog series, Facebook TAO paper, Instagram engineering posts, ByteByteGo "News Feed" video, Grokking SDI, Alex Xu Vol 2.
FAQ#
What is fan-out on write vs fan-out on read?#
Fan-out on write pushes a new post into every follower's timeline at publish time. Fan-out on read assembles the timeline at read time by querying followees' recent posts.
How do you handle the celebrity fan-out problem?#
Use a hybrid: regular accounts use push fan-out, celebrities pull at read time. The feed reader merges their cached push timeline with fresh celebrity posts on the fly.
How is a feed ranked?#
A ranker scores candidate posts by recency, engagement, affinity to author, content type, and freshness signals. A learning-to-rank model orders the final top N.
Why use Redis for timelines?#
Redis lists and sorted sets give sub-millisecond push, trim, and range reads, which match the fan-out write and feed-open read patterns exactly.
What is the p99 latency target for a news feed?#
Most production feeds target 200 to 300 ms p99 for feed open. Hitting it requires precomputed timelines, in-memory caches, and parallel calls to ranker and content services.
Related Topics#
- Pub/Sub Pattern: the messaging backbone for fan-out delivery in news feed systems
- Caching Strategies: timeline caches, pre-computed feeds, and cache invalidation strategies
- Consistent Hashing: distributes user shard ownership across feed servers without full reshuffling
Further reading#
Curated, high-credibility sources for going deeper on this topic.
- 🎥 Talk - Twitter - Timelines at Scale
- 📄 Paper - Facebook TAO - The power of the graph (USENIX ATC '13)
- ✍️ Blog - Instagram - Powered by AI: Explore recommender system