News Feed at Scale
📋 Jump to TakeawaysA news feed is the personalized, algorithmically-ranked stream of posts you see on platforms like Facebook, Twitter/X, Instagram, or LinkedIn. It aggregates content from everyone you follow, ranks it by relevance, and delivers it in near real-time. This is different from an RSS feed (a pull-based XML format for subscribing to websites). A news feed is centralized, push-based, and the platform controls what you see and in what order.
The system design challenge: how do you build this at 500M users, where a single celebrity post must reach 10M followers instantly?
Step 1: News Feed Requirements
News Feed Functional Requirements
- Users create posts (text, images, videos, links)
- Users follow/unfollow other users
- Users see a personalized home feed of posts from people they follow
- Like and comment on posts
- Media attachments (images, short videos, link previews)
- Feed supports infinite scroll with pagination
News Feed Non-Functional Requirements
- 300M daily active users (DAU)
- Feed loads in < 500ms (p99)
- Eventual consistency is acceptable (new post appears within 5 seconds)
- Users can follow up to 5,000 people
- Support celebrities with 10M+ followers
- High availability (99.99% uptime)
- Feed should feel "fresh", no stale content older than a few hours at the top
Step 2: News Feed Estimation
News Feed Traffic
- 300M DAU, each user checks feed ~10x/day → 3B feed reads/day → ~35K reads/sec
- 10% of users post daily → 30M posts/day → ~350 posts/sec
- Average user follows 200 people
Fanout Volume
- Each post fans out to average 200 followers → 30M × 200 = 6B fanout writes/day
- Celebrity post (10M followers) would require 10M writes, too expensive on write path
News Feed Storage
- Post metadata: ~1KB per post → 30M posts/day × 1KB = 30GB/day → ~11TB/year
- Feed cache entry: post ID (8 bytes) + score (8 bytes) = 16 bytes per entry
- Feed cache per user: 500 posts × 16 bytes = 8KB
- Hot users in cache (100M users): 100M × 8KB = 800GB Redis
Media
- 20% of posts have images (~2MB avg): 6M × 2MB = 12TB/day → CDN + object storage
Step 3: News Feed High-Level Design
┌────────┐ ┌──────────────┐ ┌────────────────┐
│ Client │──────▶│ API Gateway │──────▶│ Post Service │
└────────┘ └──────────────┘ └───────┬────────┘
│ │
│ ▼
│ ┌────────────────┐
│ │ Fanout Service │
│ └───────┬────────┘
│ │
│ ▼
│ ┌──────────────┐ ┌───────────────┐
│◀──────────│ Feed Service │◀──────│ Feed Cache │
│ └──────────────┘ │ (Redis) │
│ └───────────────┘
│
│ ┌──────────────┐ ┌───────────────┐
└──────────▶│ User Service │──────▶│ User Graph │
└──────────────┘ │ (who follows │
│ whom) │
└───────────────┘
Supporting services:
┌───────────────┐ ┌────────────────────┐ ┌──────────────┐
│ Media Service │ │ Notification Svc │ │ Post Storage │
│ (upload/CDN) │ │ (push on mention) │ │ (PostgreSQL) │
└───────────────┘ └────────────────────┘ └──────────────┘Flow: Publishing a Post
- Client sends post to Post Service
- Post Service writes to Post Storage (source of truth)
- Post Service emits event to Fanout Service (async via message queue)
- Fanout Service looks up followers in User Graph
- For normal users (< 500K followers): push post ID into each follower's Feed Cache
- For celebrities: skip fanout (handled at read time)
Flow: Reading the Feed
- Client requests feed from Feed Service
- Feed Service reads user's pre-built feed from Feed Cache (Redis sorted set)
- Feed Service merges in recent posts from followed celebrities (fan-out on read)
- Hydrates post IDs → full post objects from Post Storage
- Returns ranked, paginated feed to client
Step 4: News Feed Deep Dive
Fan-Out on Write vs Fan-Out on Read
| Approach | Pros | Cons |
|---|---|---|
| Fan-out on write | Fast reads, pre-computed feed | Expensive for celebrities, wasted work for inactive users |
| Fan-out on read | No write amplification | Slow reads, must merge N sources |
Hybrid approach (what Facebook/Twitter use):
- Normal users (< 500K followers): fan-out on write, push post ID to all followers' caches
- Celebrities (> 500K followers): fan-out on read, merge at query time
- Threshold is tunable; start at 500K, adjust based on latency metrics
Feed Generation and Ranking
Simple chronological feed:
- Redis sorted set, score = Unix timestamp
- Newest posts first, trivial to implement
Ranked feed (production systems):
- Score =
base_timestamp + engagement_boost + affinity_score - Affinity: how often you interact with this person
- Recency: exponential decay over time
- Engagement: likes/comments in first N minutes boost visibility
For an interview, describe chronological first, then mention ranking as an enhancement.
Feed Cache Structure
Key: feed:{user_id}
Type: Redis Sorted Set
Score: Unix timestamp (ms)
Value: post_id
Example:
ZADD feed:user123 1716000000000 "post_abc"
ZADD feed:user123 1716000060000 "post_def"
Read feed (newest 20):
ZREVRANGEBYSCORE feed:user123 +inf -inf LIMIT 0 20- Keep only the latest 500–1000 entries per user (ZREMRANGEBYRANK to trim)
- TTL of 7 days on inactive feeds. Rebuild from Post Storage on cache miss
- Total cache: ~800GB fits in a Redis cluster (10–20 nodes)
The Celebrity Problem
A user with 10M followers posting once means 10M cache writes. At 350 posts/sec from celebrities, this creates write storms.
Solution:
- Maintain a "celebrity list" (users with > 500K followers)
- When a celebrity posts, do NOT fan out. Just store the post
- When a follower reads their feed:
- Fetch pre-built feed from cache (posts from normal followees)
- Fetch recent posts from each followed celebrity (small list, typically < 50)
- Merge and rank both sets
- Cache the merged result briefly (30–60s TTL) to avoid repeated celebrity fetches
Post Storage
Feed cache stores only post IDs, not full post content.
Posts table (PostgreSQL / sharded):
post_id BIGINT PRIMARY KEY
author_id BIGINT
content TEXT
media_urls JSONB
created_at TIMESTAMP
like_count INT
comment_count INT- Shard by
post_id(snowflake ID embeds timestamp) - Read-through cache (Redis/Memcached) for hot posts
- Separating post storage from feed means updating a post (edit, delete) doesn't require touching millions of feed caches
Pagination
Cursor-based pagination using the timestamp score:
GET /feed?cursor=1716000060000&limit=20
→ ZREVRANGEBYSCORE feed:{user_id} (1716000060000 -inf LIMIT 0 20- Client passes the score of the last seen post as cursor
- Avoids offset-based issues (new posts shifting pages)
- Handles real-time insertions gracefully, no duplicates, no skips
Cache Invalidation
- New post from normal user: Fanout Service pushes post ID into each follower's sorted set
- Deleted post: Remove post ID from author's followers' caches (async, best-effort) + remove from Post Storage
- Unfollow: Lazy cleanup. Stale posts from unfollowed user get filtered at read time, eventually evicted by trim
- Cache miss: Rebuild feed from Post Storage by querying recent posts from all followed users (expensive but rare)
Media Delivery and CDN Sizing
20% of posts have images (~2MB average), generating 12TB/day of media uploads. This media must be served efficiently at read time.
CDN strategy:
- Media uploaded to object storage (S3) via pre-signed URLs
- CDN pulls from origin on first request, caches at edge
- Hot media (< 24h old, viral posts) stays cached; long-tail evicted by LRU
- Geographic routing via DNS/anycast to nearest edge PoP
Bandwidth estimation:
3B feed reads/day × 5 media items per feed page × 30% cache-miss rate
= ~4.5B media requests/day from origin
At 200KB average (resized thumbnails): 4.5B × 200KB = 900TB/day from CDN edge
CDN edge bandwidth: 900TB / 86,400s ≈ 10.4 TB/s (83 Tbps)This is served across hundreds of CDN PoPs globally, each handling a fraction. With a 70%+ cache hit rate at edge, origin bandwidth drops to ~25 Tbps.
Cost implication: At ~$0.02/GB for CDN egress, 900TB/day = ~$18K/day ($540K/month). This is why feed systems aggressively resize images (serve thumbnails in feed, full-res on tap) and use modern formats (WebP/AVIF for 30-50% size reduction).
Key Takeaways
- Use a hybrid fanout strategy: fan-out on write for normal users, fan-out on read for celebrities. This balances write cost against read latency.
- Redis sorted sets are ideal for feed caches: O(log N) insert, O(log N + M) range queries, natural ordering by timestamp.
- Store only post IDs in the feed cache: hydrate full post objects separately. This keeps cache small and decouples post updates from feed updates.
- Cursor-based pagination with timestamps avoids the duplicate/skip problems of offset pagination in real-time feeds.
- The celebrity problem is the core design challenge: recognizing and solving it demonstrates you understand write amplification (one post causing millions of cache writes) tradeoffs.
- Eventual consistency is acceptable: users won't notice a 2-5 second delay for new posts appearing in followers' feeds, and this unlocks async processing via message queues.