Facebook News Feed System Design - 1 Hour Interview Guide
Design a scalable personalized news feed system like Facebook. Covers requirements, capacity estimation, social graph, post creation, fan-out strategies, feed ranking, EdgeRank, database design, caching, and trade-offs for a 1-hour system design interview.
1-Hour Interview Roadmap
| Time | Topic |
|---|---|
| 0 – 5 min | Requirements clarification |
| 5 – 10 min | Capacity estimation |
| 10 – 18 min | High-level architecture + social graph |
| 18 – 28 min | Post creation + fan-out strategy |
| 28 – 40 min | Feed retrieval + ranking |
| 40 – 48 min | Database design + caching |
| 48 – 55 min | Scaling + multi-region |
| 55 – 60 min | Trade-offs + failure scenarios |
What Are We Building?
A personalized news feed that shows each user a ranked, continuously updated stream of posts, photos, videos, and stories from their friends, followed pages, and groups — ordered not by time alone but by relevance.
Scale reference: Facebook has 3 billion monthly active users, 2 billion daily active users, 500 million posts per day, and every user's feed must be assembled and ranked in under 200ms.
The key difference from Instagram: Facebook's feed is ranked by relevance (engagement signals, relationship strength, content type), not just reverse-chronological time order. This makes feed generation significantly more complex.
Step 1 — Requirements
Functional Requirements
| # | Requirement |
|---|---|
| 1 | Users see a personalized, ranked feed on their home page |
| 2 | Feed contains posts, photos, videos, links, and shares from friends and pages |
| 3 | Posts appear based on relevance score — not strictly newest first |
| 4 | Feed updates continuously as friends post (near real-time) |
| 5 | Users can like, comment, share, and react to posts |
| 6 | Users can create text, photo, video, and link posts |
| 7 | Users can follow friends, pages, and groups |
| 8 | Feed can be paginated — "Load more" or infinite scroll |
| 9 | Users receive notifications for interactions on their posts |
Non-Functional Requirements
| # | Requirement |
|---|---|
| 1 | Feed assembly latency < 200ms (p99) |
| 2 | Post creation must be acknowledged < 500ms |
| 3 | High availability — 99.99% uptime |
| 4 | Feed is eventually consistent — a new post may take up to 10 seconds to appear |
| 5 | Ranking must be computed at scale — 2B users × personalized scores |
| 6 | System must handle extreme skew — pages with 500M followers |
| 7 | Read-heavy — feed reads vastly outnumber post writes |
Out of Scope
- Ads and sponsored content ranking
- Video transcoding pipeline details
- Marketplace, Events, Groups feed
- Privacy and content moderation
Step 2 — Capacity Estimation
Assumptions
| Metric | Value |
|---|---|
| Daily Active Users (DAU) | 2 billion |
| Monthly Active Users (MAU) | 3 billion |
| Posts created per day | 500 million |
| Average friends per user | 200 |
| Average pages followed per user | 50 |
| Feed loads per user per day | 15 |
| Posts shown per feed load | 20 |
| Average post size (text+meta) | 1 KB |
| Read-to-write ratio | ~300:1 |
Write Volume
500 million posts/day
= ~5,800 posts/second (sustained)
= ~18,000 posts/second (peak)
Feed Read Volume
2B DAU × 15 feed loads/day = 30 billion feed reads/day
= ~347,000 feed reads/second (sustained)
= ~1,000,000 feed reads/second (peak)
Fan-out Volume
Average user: 200 friends + 50 pages they follow
Average fan-out per post: ~200 feed writes
500M posts/day × 200 = 100 billion feed writes/day
= ~1.16 million fan-out writes/second
Key Insight
The fan-out write volume (1.16M/sec) is the biggest design challenge. A public page with 500M followers posting once generates 500 million fan-out writes — this cannot be done synchronously. Feed ranking adds another layer of complexity on top of the raw assembly problem.
Step 3 — High-Level Architecture
flowchart TD
Client --> AG[API Gateway\nAuth + Rate Limiting]
AG --> POS[Post Service]
AG --> FES[Feed Service]
AG --> USS[User & Graph Service]
AG --> RES[Reaction Service]
AG --> NOS[Notification Service]
POS --> KF[Kafka\npost.created events]
KF --> FO[Fan-out Service]
KF --> NW[Notification Workers]
FO --> FDB[(Feed Store\nRedis + Cassandra)]
FO --> RANK[Ranking Service\nScore Computation]
RANK --> FDB
FES --> FC[(Feed Cache\nRedis)]
FC -->|miss| FDB
FDB -->|miss| PDB[(Post Store\nCassandra)]
USS --> SG[(Social Graph\nTAO / Cassandra)]
POS --> PDB
RES --> PDB
NW --> FCM[FCM / APNs]
NW --> NDB[(Notification Store)]
Component Responsibilities
| Component | Responsibility |
|---|---|
| API Gateway | JWT auth, rate limiting, routing |
| Post Service | Create, edit, delete posts; publish fan-out events to Kafka |
| Fan-out Service | Consume post events; write post IDs to followers' feed stores |
| Ranking Service | Compute relevance scores for posts per user; sort feed by score |
| Feed Service | Assemble and return ranked feed from cache or store |
| User & Graph Svc | Manage friendships, page follows, group memberships (TAO/social graph) |
| Reaction Service | Handle likes, reactions, comments; update engagement signals |
| Notification Svc | Async delivery of interaction notifications |
| Social Graph | The friendship + follow relationship store — core of fan-out and feed assembly |
Step 4 — Social Graph
Why the Social Graph Is Central
Every fan-out decision requires answering: "Who are all the followers of this user?"
At Facebook's scale, each user has ~200 friends on average, but pages can have 500M followers. The social graph must support:
GET followers(user_id)— return all followers (for fan-out)GET following(user_id)— return all accounts this user follows (for feed assembly)- Sub-millisecond reads — called on every post and every feed load
Facebook's TAO (The Associations and Objects)
Facebook built a custom graph storage system called TAO to serve the social graph:
flowchart LR
CLIENT["Client App"]
CACHE["TAO Cache Layer"]
DB["MySQL Social Graph DB"]
RESPONSE["Response"]
CLIENT --> CACHE
CACHE -->|cache hit| RESPONSE
CACHE -->|cache miss| DB
DB --> CACHE
CACHE --> RESPONSE
| Layer | Technology | Role |
|---|---|---|
| Hot cache | TAO in-memory | Sub-ms reads for popular users / pages |
| Warm tier | Memcached / Redis | Region-local cache of graph edges |
| Cold tier | MySQL / Cassandra | Persistent storage of all friendships and follows |
For interview purposes: Cassandra is a good answer — partition by user_id, cluster by friend_id, supports fast range reads of all friends.
Social Graph Schema (Cassandra)
Table: followers
user_id BIGINT (the followed account)
follower_id BIGINT
created_at TIMESTAMP
edge_type VARCHAR (FRIEND | FOLLOW | GROUP_MEMBER)
PRIMARY KEY (user_id, follower_id)
Table: following
user_id BIGINT (the user who follows)
followed_id BIGINT
created_at TIMESTAMP
strength FLOAT (interaction frequency — used for ranking)
PRIMARY KEY (user_id, followed_id)
The strength field on the following table captures how often two users interact — used by the ranking service as a signal.
Step 5 — Post Creation Flow
sequenceDiagram
participant User
participant PS as Post Service
participant PDB as Post Store (Cassandra)
participant KF as Kafka
participant FO as Fan-out Service
participant FDB as Feed Store (Redis/Cassandra)
User->>PS: Create post {text, media_url, privacy}
PS->>PS: Validate + assign post_id (Snowflake)
PS->>PDB: Persist post
PDB-->>PS: OK
PS-->>User: ACK {post_id, status: PUBLISHED}
PS->>KF: Publish post.created {post_id, author_id, timestamp}
KF->>FO: Consume event
FO->>SG: GET followers(author_id) — paginated
loop For each follower batch (1000 at a time)
FO->>FDB: Append post_id to follower's feed
end
Key points:
- User gets an ACK as soon as the post is persisted — fan-out happens asynchronously
- Fan-out is batched — process followers in pages of 1,000
- Large pages (500M followers) are handled specially — see hybrid fan-out below
Step 6 — Fan-out Strategy
The Core Challenge
| Scenario | Fan-out writes | Problem |
|---|---|---|
| Regular user (200 friends) | 200 writes | Trivial — fan-out on write |
| Public figure (1M followers) | 1M writes | Significant — needs async batching |
| Celebrity page (500M followers) | 500M writes | Impossible synchronously |
Hybrid Fan-out (Facebook's approach)
flowchart TD
Post -->|author follower count?| FC{Follower\nCount}
FC -->|< 100K\nRegular user| FW[Fan-out on WRITE\nPush to all feed stores\nasync, batched]
FC -->|> 100K\nCelebrity / Page| FR[Fan-out on READ\nDo NOT push\nFetch at read time]
FeedLoad[User loads feed] --> ASS[Feed Assembler]
ASS -->|Pre-built cache| RegPosts[Regular friends' posts\nfrom Redis feed store]
ASS -->|Pull live| CelPosts[Celebrity / page posts\nfetch top N from Post DB]
ASS -->|Merge + Rank| RankedFeed[Ranked Feed]
RankedFeed --> User
Push model (regular users < 100K followers)
- Fan-out Service consumes post event from Kafka
- Fetches follower list in batches of 1,000
- Appends
post_id+ raw score to each follower's feed sorted set in Redis - Feed store holds the most recent 1,000 post IDs per user
Pull model (celebrities > 100K followers)
- No fan-out writes — celebrity's post is never pushed to individual feeds
- When Bob opens his feed, Feed Assembler pulls the top 50 posts from each celebrity he follows
- Merges them with his pre-built regular feed
- Result is ranked and returned
Why this boundary?
At 100K followers, a single post = 100K Redis writes. At peak (18K posts/sec from high-follower accounts), this is 1.8 billion writes/second — unsustainable. The hybrid model caps fan-out writes to a manageable level.
Step 7 — Feed Ranking
Why Ranking Matters
Facebook's feed is not reverse-chronological. A post from your best friend posted 3 hours ago ranks above a post from a distant acquaintance from 5 minutes ago. This is what keeps users engaged.
EdgeRank — Facebook's Original Algorithm
Facebook's original ranking model was called EdgeRank:
Score(post, user) = Affinity × Weight × Time Decay
| Signal | Meaning | Example |
|---|---|---|
| Affinity | How close is the user to the post's author? | Message history, mutual friends, photo tags |
| Weight | How engaging is the content type? | Video > Photo > Link > Status update |
| Time Decay | How recent is the post? | Posts from 1 hour ago score higher than 1 week ago |
Modern Feed Ranking (ML-based)
Today Facebook uses a multi-stage ML ranking pipeline:
flowchart LR
FeedStore[Raw Feed\n1000 candidates] --> S1[Stage 1\nLight Scorer\nFilter to 500]
S1 --> S2[Stage 2\nML Ranker\nNeuralNet\nRank top 100]
S2 --> S3[Stage 3\nDiversity Filter\nAvoid same-author clusters]
S3 --> S4[Final Feed\n20 posts]
S4 --> User
| Stage | Method | Candidates In | Candidates Out | Latency |
|---|---|---|---|---|
| Stage 1 | Rule-based filter | 1,000 | 500 | < 5ms |
| Stage 2 | Lightweight ML model | 500 | 100 | < 20ms |
| Stage 3 | Deep neural network | 100 | 20 | < 50ms |
| Stage 4 | Diversity + dedup | 20 | 20 (reordered) | < 5ms |
Total ranking budget: ~80ms out of the 200ms total latency budget.
Ranking Signals
| Signal Category | Examples |
|---|---|
| Author signals | Affinity score, mutual friends, interaction history |
| Content signals | Media type, post length, link domain quality |
| Engagement signals | Early likes/comments in first 30 minutes, shares, reaction type |
| User signals | Time of day, device type, recently liked content type |
| Recency | Time since post was created |
Pre-computed vs Real-time Ranking
| Approach | When | Pros | Cons |
|---|---|---|---|
| Pre-computed | Fan-out on write | Fast feed load | Stale scores; heavy compute |
| Real-time | At feed load time | Fresh signals | Expensive at 1M req/sec |
| Hybrid | Store raw candidates; rank at read time | Balance of both | Standard production approach |
Chosen approach: Store post IDs (without scores) in feed store during fan-out. Rank the top 1,000 candidates at feed load time using pre-computed affinity scores from a separate user-affinity store.
Step 8 — Feed Retrieval Flow
sequenceDiagram
participant Bob
participant FS as Feed Service
participant FC as Feed Cache (Redis)
participant FDB as Feed Store (Cassandra)
participant RANK as Ranking Service
participant PDB as Post Store
Bob->>FS: GET /feed?page=1
FS->>FC: GET feed:{bob_id}
alt Cache hit
FC-->>FS: [post_id_1, post_id_2, ..., post_id_1000]
else Cache miss
FS->>FDB: Query timeline:{bob_id}
FDB-->>FS: [post_id list]
FS->>FC: Populate cache
end
FS->>RANK: Rank candidates (top 1000 post IDs, bob's context)
RANK-->>FS: Top 20 ranked post IDs
FS->>PDB: Batch fetch post details (top 20)
PDB-->>FS: Post objects
FS-->>Bob: Ranked feed (20 posts + pagination cursor)
Feed Pagination
Cursor-based pagination (not offset-based):
First load: GET /feed
Response: { posts: [...20 posts], next_cursor: "post_id_XYZ" }
Next page: GET /feed?cursor=post_id_XYZ
Response: { posts: [...20 more posts], next_cursor: "post_id_ABC" }
Why cursor over offset? With offset pagination, new posts inserted at the top shift all positions — users see duplicate or skipped posts. Cursor-based pagination is stable across writes.
Step 9 — Database Design
Database Selection
| Data | Database | Reason |
|---|---|---|
| Users & authentication | PostgreSQL | Structured, ACID, strong consistency |
| Social graph | Cassandra / TAO | Wide rows — billions of edges; fast range reads |
| Posts & media metadata | Cassandra | Write-heavy, time-ordered, append-only |
| Feed timeline (raw) | Cassandra | Wide rows per user — time-ordered post IDs |
| Feed cache (hot) | Redis | Sub-ms feed assembly for active users |
| Affinity scores | Redis + offline store | Pre-computed per (user, friend) pair — queried at ranking time |
| Reactions & comments | Cassandra | High write volume, time-ordered |
| Notifications | Cassandra | Append-only, high volume |
Post Table (Cassandra)
Table: posts
Partition key: author_id BIGINT
Clustering key: post_id BIGINT (Snowflake — time-ordered)
Columns:
content_type VARCHAR (TEXT | PHOTO | VIDEO | LINK | SHARE)
text_body TEXT
media_urls LIST<TEXT> (CDN URLs)
link_url TEXT
privacy VARCHAR (PUBLIC | FRIENDS | CLOSE_FRIENDS | ONLY_ME)
like_count COUNTER
comment_count COUNTER
share_count COUNTER
created_at TIMESTAMP
is_deleted BOOLEAN
Feed Timeline Table (Cassandra)
Table: feed_timeline
Partition key: user_id BIGINT
Clustering key: score FLOAT DESC (pre-computed raw score)
post_id BIGINT (time-ordered tiebreaker)
Columns:
author_id BIGINT
inserted_at TIMESTAMP
Query pattern: SELECT post_id FROM feed_timeline WHERE user_id = ? LIMIT 1000
Wide rows — one partition per user, containing up to 1,000 post IDs ordered by raw score descending.
Reaction Table (Cassandra)
Table: reactions
Partition key: post_id BIGINT
Clustering key: user_id BIGINT
reacted_at TIMESTAMP
Columns:
reaction_type VARCHAR (LIKE | LOVE | HAHA | WOW | SAD | ANGRY)
PRIMARY KEY (post_id, user_id)
Unique per (post_id, user_id) — idempotent upsert prevents double reactions.
Step 10 — Caching Strategy
Redis Cache Design
| Cache | Key Pattern | TTL | Contents |
|---|---|---|---|
| Feed cache | feed:{user_id} |
24 hrs | Sorted set — score → post_id (top 1,000) |
| Post details | post:{post_id} |
7 days | Full post object |
| User profile | user:{user_id} |
1 hr | Profile + counts |
| Affinity score | affinity:{user_a}:{user_b} |
6 hrs | Pre-computed relationship strength (0.0 – 1.0) |
| Reaction count | reactions:{post_id} |
Eventual | Counter per reaction type |
| Celebrity recent posts | celeb_posts:{user_id} |
5 min | Last 50 post IDs — for pull-based fan-out merge |
| Trending posts | trending:{region} |
10 min | Top 100 post IDs by engagement in last 1h |
Hot Post Problem
A viral post receives millions of reactions per minute. A single Cassandra row becomes a hotspot.
Solution:
- All reaction increments hit Redis counters only (
INCR reactions:{post_id}:LIKE) - Async worker flushes counters to Cassandra in 30-second batches
- Post read always goes to Redis first — no DB read under hot conditions
Cold Start Problem
A new user has no feed history and no social graph data.
Solution:
- On first feed load, trigger a full pull-based feed assembly from scratch
- Show suggested content (trending, popular in region) while personalized feed builds
- After the user makes 5+ interactions, affinity scores bootstrap and personalized feed takes over
Step 11 — Scaling
Read Scaling
flowchart TD
LB[Load Balancer] --> F1[Feed Service Pod]
LB --> F2[Feed Service Pod]
LB --> F3[Feed Service Pod]
F1 & F2 & F3 --> RC[(Redis Cluster\nFeed + Affinity Cache)]
RC -->|miss| CS[(Cassandra\nFeed Timeline)]
CS -->|batch fetch| PDB[(Post Store\nCassandra)]
- Feed Service is stateless — horizontally scalable
- Redis Cluster shards feed keys across nodes — 347K reads/sec is handled by multiple Redis nodes
- Cassandra read replicas absorb cache misses
Fan-out Scaling
flowchart LR
KF[Kafka\npost.created\n100+ partitions] --> FO1[Fan-out Worker]
KF --> FO2[Fan-out Worker]
KF --> FO3[Fan-out Worker]
FO1 & FO2 & FO3 -->|batch writes| RC[(Redis\nFeed Caches)]
FO1 & FO2 & FO3 -->|async| CS[(Cassandra)]
- Kafka partitioned by
author_id— all fan-out for one author goes to one partition (ordering) - Fan-out workers process follower lists in parallel batches of 1,000
- Redis pipeline writes — batch 1,000 ZADD operations in one round trip
Ranking Service Scaling
2B users × 347K feed reads/sec
Each feed rank takes 80ms of compute
Required: 347K × 80ms = 27,760 core-seconds/sec
= ~28,000 CPU cores for ranking alone
Solution:
- Pre-compute and cache affinity scores offline (batch jobs every 6 hours)
- Lightweight online ranking uses pre-computed scores — avoids real-time graph traversal
- Ranking service is stateless and horizontally scalable
Database Sharding
| Database | Shard Key | Strategy |
|---|---|---|
| Post store | author_id |
Consistent hashing — all posts by one author co-located |
| Feed store | user_id |
Consistent hashing — one user's feed in one shard |
| Social graph | user_id |
Consistent hashing — all edges for one user together |
| Reactions | post_id |
Consistent hashing — all reactions per post co-located |
Step 12 — Multi-Region Deployment
Facebook operates in every continent. A user in Singapore must not wait for a round trip to a US data center on every feed load.
flowchart TD
Users --> GLB[Global Load Balancer\nAnyCast DNS]
GLB -->|APAC users| APAC[APAC Region\nFull Stack]
GLB -->|EU users| EU[EU Region\nFull Stack]
GLB -->|US users| US[US Region\nPrimary]
US -->|async replication| APAC
US -->|async replication| EU
| Strategy | Detail |
|---|---|
| Read local | Feed reads served from local region replica — sub-50ms |
| Write primary | Post writes go to primary region first, then replicate async |
| Eventual consistency | A post created in APAC may take 5–10 seconds to appear in EU feeds |
| Conflict resolution | Last-write-wins for post edits; reaction counts eventually converge |
Step 13 — Failure Scenarios
| Failure | Impact | Mitigation |
|---|---|---|
| Redis (feed cache) down | Feed reads fall back to Cassandra | Redis Sentinel / Cluster failover; Cassandra is always the source of truth |
| Kafka down | Fan-out and notifications stall | Producers buffer locally; Kafka 3-broker cluster with replication |
| Fan-out workers down | New posts don't fan out | Kafka retains events; workers catch up after restart |
| Ranking service down | Feed served unranked (reverse-chrono fallback) | Graceful degradation — skip ranking, return time-ordered feed |
| Cassandra node down | Partial data unavailable | Replication factor 3; coordinator routes to healthy replicas |
| Hot shard (viral post) | Single Redis key overwhelmed | Redis counters → async DB flush; local in-process counters on service pods |
| Social graph service down | Fan-out cannot resolve followers | Cached follower lists in Redis; stale by minutes — acceptable |
| Cross-region replication lag | Feed shows stale data in remote region | Acceptable (eventual consistency); show "X minutes ago" timestamp to manage expectation |
Final Architecture
flowchart TD
Mobile[Mobile / Web Clients] --> GLB[Global Load Balancer]
GLB --> AG[API Gateway\nAuth + Rate Limiting]
AG --> POS[Post Service]
AG --> FES[Feed Service]
AG --> USS[User & Graph Service]
AG --> RES[Reaction Service]
POS --> KF[Kafka Cluster\nauthor_id partitioned]
KF --> FO[Fan-out Workers × N]
KF --> NW[Notification Workers]
FO --> RC[(Redis Cluster\nFeed Cache)]
FO --> FDB[(Cassandra\nFeed Timeline)]
FES --> RC
RC -->|miss| FDB
FES --> RANK[Ranking Service\nML Scorer]
RANK --> AFF[(Redis\nAffinity Scores)]
FES --> PDB[(Cassandra\nPost Store)]
USS --> SG[(TAO / Cassandra\nSocial Graph)]
RES --> PDB
NW --> FCM[FCM / APNs]
NW --> NDB[(Cassandra\nNotifications)]
PDB --> CDN[CDN\nMedia Delivery]
Technology Stack
| Layer | Technology |
|---|---|
| API Gateway | Custom proxy / NGINX / Envoy |
| Post + Feed services | C++ / Python / Go (Facebook uses Hack/C++) |
| Message queue | Apache Kafka |
| Feed + affinity cache | Redis Cluster |
| Social graph | TAO (Facebook-custom) / Cassandra |
| Posts, feed, reactions | Apache Cassandra |
| User data | MySQL / PostgreSQL |
| Ranking / ML | PyTorch models served via Triton inference server |
| Object storage | Facebook's own Haystack + f4 / Amazon S3 |
| CDN | Facebook CDN / Cloudflare |
| Monitoring | Scuba + ODS (Facebook internal) / Prometheus+Grafana |
| Deployment | Multi-region data centers (own infrastructure) |
Key Trade-Offs
| Decision | Option A | Option B | Choice & Reason |
|---|---|---|---|
| Feed ordering | Reverse-chronological | ML-ranked by relevance | ML-ranked — higher engagement; but adds ranking latency budget |
| Fan-out strategy | Push (write) only | Pull (read) only | Hybrid — push for regular users, pull for celebrities |
| Fan-out timing | Synchronous (block post ACK) | Async via Kafka | Async — user gets ACK in < 500ms; fan-out happens after |
| Feed ranking | Real-time scoring | Pre-computed + cached scores | Hybrid — cache affinity scores; light online ranking at read time |
| Consistency (feed) | Strong — user sees post immediately | Eventual — up to 10s delay | Eventual — acceptable UX; enables async fan-out |
| Reaction counts | Exact DB counts | Redis counter + async flush | Redis counter — hot posts would lock DB rows under exact counting |
| Social graph storage | Relational DB (MySQL) | Distributed graph store (TAO) | TAO/Cassandra — billions of edges cannot be joined in SQL at runtime |
| Pagination | Offset-based | Cursor-based | Cursor-based — stable under concurrent inserts; no duplicate/skipped posts |
Facebook News Feed vs Instagram Feed
| Dimension | Facebook News Feed | Instagram Feed |
|---|---|---|
| Ordering | ML-ranked by relevance | ML-ranked (also not chronological) |
| Content types | Text, photo, video, link, share, events | Photo and video only |
| Social model | Friends (bidirectional) + Pages + Groups | Follow (unidirectional) |
| Fan-out threshold | ~100K followers | ~1M followers |
| Graph complexity | High — groups, pages, events, friends | Lower — follow graph only |
| Ranking signals | Many — relationship depth, content type | Fewer — engagement + recency |
Common Interview Mistakes
- ❌ Designing a purely chronological feed — Facebook's feed is ranked
- ❌ Not explaining the fan-out problem and the celebrity/page edge case
- ❌ Doing fan-out synchronously — blocks post creation under load
- ❌ Using SQL joins to traverse the social graph at read time — too slow at billions of edges
- ❌ No caching layer — 347K feed reads/sec cannot hit Cassandra directly
- ❌ Storing reaction counts as row-level DB increments — hotspot problem under viral posts
- ❌ Offset-based pagination — skips/duplicates posts when new content is inserted
- ❌ No ranking service — forgetting that Facebook's feed is not time-ordered
- ❌ No multi-region discussion — Facebook is a global product
Interview Questions
- How is Facebook's news feed different from a simple reverse-chronological timeline?
- What is EdgeRank and how does it influence feed ranking?
- What is the fan-out problem? How do you solve it for users with 500M followers?
- Why is fan-out done asynchronously via Kafka rather than synchronously?
- How does the hybrid fan-out model work in practice?
- How do you rank 1,000 candidate posts in under 80ms?
- What affinity signals are used to compute relationship strength?
- Why is the social graph stored in a distributed store instead of MySQL?
- How do you handle hot posts receiving millions of reactions per minute?
- Why is cursor-based pagination better than offset pagination for feeds?
- How do you serve the feed for a new user with no social graph (cold start)?
- What happens when the Redis feed cache goes down?
- How does multi-region replication work for the feed?
- How does the feed store in Cassandra support efficient pagination?
- What is the ranking pipeline — how many stages and what does each stage do?
Summary
| Concern | Solution |
|---|---|
| Feed assembly | Hybrid fan-out — push for regular users, pull for celebrities |
| Feed ranking | Multi-stage ML pipeline: rule filter → lightweight ML → deep neural net |
| Ranking signals | Affinity score, content weight, engagement signals, recency, time decay |
| Social graph | Cassandra / TAO — wide rows, billions of edges, sub-ms reads from cache |
| Post storage | Cassandra — partitioned by author_id, clustered by post_id (time) |
| Feed cache | Redis Sorted Set per user — top 1,000 post IDs with raw scores |
| Reaction counts | Redis counters — async flush to Cassandra every 30 seconds |
| Fan-out timing | Async via Kafka — post ACK returned before fan-out completes |
| Pagination | Cursor-based — stable under concurrent writes |
| Scale | Stateless services + Kafka + Redis + Cassandra sharded by key |
| Multi-region | Read local, write primary, async replication, eventual consistency |
The core principle: Facebook News Feed is a ranking problem, not just a storage problem. Fast assembly of candidates (fan-out + cache) plus low-latency relevance scoring is what makes it work at 2 billion daily users.