Stage 4 · Provision
System Design Interviews
Design a News Feed
Fanout-on-write, fanout-on-read, ranking, denormalized timelines, and cache warming.
Requirements
A news feed aggregates content from users a person follows. When User A posts, it should appear in the feeds of all their followers. Key challenge: a celebrity with millions of followers creates a fanout problem.
Functional:
- Post content (text, images, video)
- View personalized feed (posts from followed users)
- Like, comment, share posts
Non-functional:
- Feed generation: < 200ms
- Post visibility: < 5 seconds
- 500M daily active users
- 1M posts per day
- Read/Write ratio: 100:1The read-to-write ratio is extreme. Most operations are feed reads. This heavily influences the design toward caching and denormalization.
Fanout on Write
Fanout on write (push model) pre-computes each user's feed. When User A posts, the system immediately writes the post ID to the feed of every follower. Reading the feed is a simple list lookup. This optimizes read time at the cost of write time.
def create_post(user_id: str, content: str):
# Store the post
post = store_post(user_id, content)
# Get all followers
followers = get_followers(user_id)
# Fanout: write post ID to each follower's feed
for follower_id in followers:
redis.lpush(f"feed:{follower_id}", post.id)
redis.ltrim(f"feed:{follower_id}", 0, 799) # Keep last 800
# For celebrities: defer fanout to background job
if len(followers) > 10_000:
queue_fanout_job(user_id, post.id)For regular users, fanout is fast. For celebrities (millions of followers), defer fanout to a background job to avoid blocking the post creation.
Fanout on Read
Fanout on read (pull model) computes the feed on read. When User B opens their feed, the system fetches recent posts from all users B follows, merges them, and returns the result. This optimizes write time at the cost of read time.
def get_feed(user_id: str):
# Get list of followed users
following = get_following(user_id)
# Fetch recent posts from each followed user
posts = []
for followed_id in following:
user_posts = get_recent_posts(followed_id, limit=10)
posts.extend(user_posts)
# Sort by timestamp and return top 20
posts.sort(key=lambda p: p.created_at, reverse=True)
return posts[:20]Fanout on read is simple but slow for users following many people. Each feed request queries many users.
Hybrid Approach
Production systems use a hybrid: fanout on write for regular users, fanout on read for celebrities. When a celebrity posts, their followers' feeds are computed on read by merging the celebrity's posts with their pre-computed feed.
Regular user posts (few followers):
Fanout on write → Push to all followers' feed caches
Celebrity posts (millions of followers):
1. Store post in celebrity's post list
2. Flag user as "celebrity"
3. On feed read: merge celebrity posts dynamically
Feed read for regular user:
Pre-computed feed (from regular user fanout)
+ Merged celebrity posts (from celebrities they follow)
= Complete personalized feedThis hybrid approach handles both cases efficiently. Regular users get fast reads from pre-computed feeds. Celebrity posts are merged dynamically to avoid massive fanout.
Feed Ranking
- Chronological — Simplest, no ranking. Posts in order of time.
- Edge ranking — Facebook's approach. Score based on interaction probability.
- ML ranking — Use a trained model to predict engagement.
- Hybrid — Rank top 100 posts by ML, show rest chronologically.
Cache Warming
Pre-compute and cache feeds for active users. During off-peak hours, update feeds for users who will be active soon. This ensures fast feed reads even for users with complex follow graphs.
The entire news feed design revolves around the celebrity problem. A single post by a celebrity with 100M followers must appear in 100M feeds. This is why hybrid fanout is essential.
Mark this lesson complete to store local progress and unlock a cleaner resume path the next time you visit.