Design a News Feed

Hard 30 min read

Problem Statement & Requirements

Why News Feed Design Is a Top Interview Question

The Challenge: Generating a personalized, ranked feed for hundreds of millions of users in real-time involves nearly every major system design concept: caching, fan-out, ranking, storage, and CDN delivery.

Real Scale: Facebook's News Feed serves 2 billion users. Each feed request aggregates posts from hundreds of friends and pages, ranks them, and returns results in under 200ms.

Real-World Analogy

Think of a news feed like a personalized newspaper editor:

  • Posts = News stories submitted by reporters (your friends)
  • Fan-out = The printing press distributing papers to subscribers
  • Ranking = The editor deciding what goes on the front page
  • Cache = Pre-printed papers ready for immediate delivery
  • CDN = Regional distribution centers closer to readers

Functional Requirements

Publish Posts

Users can create posts with text, images, and videos. Posts appear in followers' feeds.

View Feed

Users see a ranked, personalized feed composed of posts from friends, pages, and groups they follow.

Interactions

Users can like, comment, and share posts. These interactions influence feed ranking.

Infinite Scroll

Feed supports paginated loading. As users scroll, older posts are fetched dynamically.

Feed Generation vs Fan-Out

Feed Generation Pipeline
User Publishes New post Post Service Store + validate Post Database Fan-Out Service Distribute to followers' feeds Feed Cache User A's feed Feed Cache User B's feed Feed Cache User C's feed User Opens App GET /feed Feed Service Fetch + rank Feed Cache Pre-computed Ranking Service ML-based scoring Write Path (Publishing) Read Path (Viewing Feed)

Push vs Pull Model

Push Model (Fan-Out on Write) vs Pull Model (Fan-Out on Read)
Push Model (Fan-Out on Write) Post Cache A Cache B Cache C Pros: Fast reads (pre-computed) Simple read path Cons: Slow writes for celebrities Wasted work for inactive users Pull Model (Fan-Out on Read) User Friend 1 Friend 2 Friend N Pros: Fast writes (just store post) No wasted computation Cons: Slow reads (gather + rank) Complex read path

The Hybrid Approach (Industry Standard)

Facebook, Twitter, and Instagram all use a hybrid model:

  • Regular users (under 10K followers): Push model. Fan out posts to all followers' feed caches on write.
  • Celebrities/pages (10K+ followers): Pull model. Their posts are fetched and merged at read time to avoid fan-out storms.
  • This hybrid approach keeps write latency under 1 second for 99.9% of posts while handling celebrity accounts gracefully.

Feed Ranking Algorithm

feed_ranking.py
import math
from datetime import datetime, timedelta
from dataclasses import dataclass

@dataclass
class Post:
    post_id: str
    author_id: str
    created_at: datetime
    likes: int
    comments: int
    shares: int
    post_type: str  # 'text', 'image', 'video'


class FeedRanker:
    """
    Multi-signal feed ranking algorithm.

    Score = affinity * edge_weight * time_decay

    Affinity:    How close is the viewer to the author?
    Edge Weight: How engaging is this type of content?
    Time Decay:  How fresh is the post?
    """

    # Content type weights (based on engagement data)
    TYPE_WEIGHTS = {
        "video": 1.5,
        "image": 1.2,
        "text": 1.0,
        "link": 0.8,
    }

    def calculate_score(self, post: Post, viewer_id: str) -> float:
        """Calculate ranking score for a post relative to a viewer."""
        affinity = self._get_affinity(viewer_id, post.author_id)
        engagement = self._engagement_score(post)
        freshness = self._time_decay(post.created_at)
        type_boost = self.TYPE_WEIGHTS.get(post.post_type, 1.0)

        return affinity * engagement * freshness * type_boost

    def _get_affinity(self, viewer_id: str, author_id: str) -> float:
        """
        Relationship strength between viewer and author.
        Based on: messages sent, profile views, likes, comments.
        Range: 0.0 to 1.0
        """
        # In production: ML model trained on interaction data
        interactions = get_interaction_count(viewer_id, author_id)
        return min(1.0, interactions / 100)

    def _engagement_score(self, post: Post) -> float:
        """Weighted sum of engagement signals."""
        return (
            post.likes * 1.0 +
            post.comments * 2.0 +  # comments are more valuable
            post.shares * 3.0     # shares indicate high quality
        )

    def _time_decay(self, created_at: datetime) -> float:
        """
        Exponential time decay.
        Half-life of ~6 hours: a post loses half its
        freshness score every 6 hours.
        """
        hours_old = (datetime.utcnow() - created_at).total_seconds() / 3600
        half_life = 6.0
        return math.pow(0.5, hours_old / half_life)

    def rank_feed(self, posts: list, viewer_id: str, limit: int = 50) -> list:
        """Rank and return top posts for the viewer's feed."""
        scored = [
            (self.calculate_score(post, viewer_id), post)
            for post in posts
        ]
        scored.sort(key=lambda x: x[0], reverse=True)
        return [post for _, post in scored[:limit]]
Feed Ranking Signal Weights
Affinity 35% - Relationship strength Engagement 28% - Likes, comments, shares Freshness 22% - Time decay Content 15% - Post type boost

Media Storage & CDN

Media Pipeline

  • Upload: Client uploads to a media service via pre-signed S3 URLs
  • Processing: Resize images (thumbnail, medium, full), transcode videos
  • Storage: S3 for originals, CloudFront/CDN for optimized versions
  • Delivery: CDN serves media from the edge node closest to the user
  • Lazy loading: Feed initially loads text + thumbnails; full media loads on scroll

Cache Strategy for Feed

feed_cache.py
import redis
import json

class FeedCache:
    """
    Multi-tier caching strategy for the news feed.

    Tier 1: Pre-computed feed (Redis sorted set per user)
    Tier 2: Post content cache (Redis hash)
    Tier 3: Social graph cache (who follows whom)
    """

    def __init__(self):
        self.redis = redis.Redis(host='redis-cluster')
        self.FEED_SIZE = 500  # Cache last 500 post IDs per user
        self.FEED_TTL = 86400  # 24 hours

    def add_to_feed(self, user_id: str, post_id: str, score: float):
        """Push a new post into a user's feed cache (fan-out on write)."""
        key = f"feed:{user_id}"
        # Sorted set: score determines feed ordering
        self.redis.zadd(key, {post_id: score})
        # Trim to keep only the most recent N posts
        self.redis.zremrangebyrank(key, 0, -(self.FEED_SIZE + 1))
        self.redis.expire(key, self.FEED_TTL)

    def get_feed(self, user_id: str, page: int = 0,
                  size: int = 20) -> list:
        """Fetch a page of feed items for the user."""
        key = f"feed:{user_id}"
        start = page * size
        end = start + size - 1

        # Get post IDs from sorted set (highest score first)
        post_ids = self.redis.zrevrange(key, start, end)

        if not post_ids:
            # Cache miss: regenerate from database
            return self._rebuild_feed(user_id, page, size)

        # Fetch full post content (Tier 2 cache)
        return self._hydrate_posts(post_ids)

    def _hydrate_posts(self, post_ids: list) -> list:
        """Fetch full post data using pipeline for efficiency."""
        pipe = self.redis.pipeline()
        for pid in post_ids:
            pipe.hgetall(f"post:{pid}")
        return pipe.execute()

Scaling the Feed System

Database Sharding

Shard post data by user_id. Feed cache sharded by viewer's user_id. Use consistent hashing for even distribution across shards.

Async Fan-Out Workers

Fan-out is done by a pool of workers consuming from Kafka. Scale workers independently based on queue depth.

Multi-Region Deployment

Deploy feed services in multiple regions. Use eventual consistency for the social graph. CDN caches media globally.

Rate Limiting Publishes

Limit post frequency to prevent spam and reduce fan-out load. Typical: 10 posts/hour per user.

Practice Problems

Medium Celebrity Problem

A celebrity with 100 million followers posts an update. How do you handle the fan-out without overwhelming the system?

  1. Calculate the cost of fan-out-on-write for this user
  2. Design a hybrid approach that handles celebrities differently
  3. How do you determine the threshold for "celebrity" treatment?

Fan-out-on-write for 100M followers would require 100M cache writes per post. Instead, store the celebrity's posts separately and merge them at read time. Consider a follower count threshold (e.g., 10K) to switch strategies.

# Hybrid Fan-Out Strategy
# Threshold: users with > 10,000 followers use pull model

def publish_post(author_id, post):
    follower_count = get_follower_count(author_id)

    if follower_count < 10000:
        # Push model: fan out to all followers
        for follower in get_followers(author_id):
            feed_cache.add_to_feed(follower, post.id, post.score)
    else:
        # Pull model: just store in celebrity posts table
        celebrity_posts.store(author_id, post)

def get_feed(user_id):
    # Get pre-computed feed (from push model)
    feed = feed_cache.get_feed(user_id)
    # Merge celebrity posts (pull model)
    celeb_followings = get_celebrity_followings(user_id)
    for celeb_id in celeb_followings:
        recent = celebrity_posts.get_recent(celeb_id)
        feed.extend(recent)
    # Re-rank and return
    return ranker.rank_feed(feed, user_id)

Hard Content Moderation

Design a content moderation pipeline that detects and filters harmful content before it appears in feeds. Consider both automated and human review.

  1. Where in the pipeline do you perform moderation?
  2. How do you handle false positives without delaying legitimate posts?
  3. How do you scale human review for edge cases?

Use a multi-stage pipeline: fast ML classifiers for obvious cases, then human review for borderline content. Consider a "shadow ban" approach where flagged content is hidden from feeds but not deleted immediately.

# Content Moderation Pipeline
# Stage 1: Pre-publish ML filter (sync, < 100ms)
#   - Text: NLP classifier for hate speech, spam
#   - Images: Computer vision for nudity, violence
#   - Confidence > 0.95: Auto-reject
#   - Confidence < 0.3: Auto-approve
#   - Between: Queue for human review

# Stage 2: Post-publish async review
#   - Content published immediately (good UX)
#   - Async worker runs deeper analysis
#   - If flagged: remove from feeds, notify author

# Stage 3: Human review queue
#   - Priority queue sorted by reach (followers * views)
#   - SLA: 4 hours for high-reach, 24h for low-reach
#   - Reviewer decisions train the ML model (feedback loop)

Hard Trending Topics

Add a "Trending" feature that surfaces popular topics in real-time. How do you detect trends across billions of posts?

  1. How do you define what is "trending"?
  2. What data structures enable real-time trend detection?
  3. How do you prevent manipulation of trending topics?

A topic is "trending" when its mention velocity exceeds its historical baseline. Use Count-Min Sketch for approximate counting and sliding time windows. Compare current counts against exponential moving averages.

# Trending Detection Pipeline
# 1. Extract entities/hashtags from posts (NLP)
# 2. Stream to Kafka topic "entity-mentions"
# 3. Flink job: count mentions per 5-min window
# 4. Compare to 7-day exponential moving average
# 5. Trending if: current_rate > 3 * avg_rate

# Data structures:
# - Count-Min Sketch: space-efficient approximate counting
# - Min-Heap: top-K trending topics
# - Sliding window: HyperLogLog per entity per time bucket

# Anti-manipulation:
# - Weight by unique users, not total mentions
# - Discount accounts < 30 days old
# - Geographic diversity requirement

Quick Reference

News Feed Design Summary

Component Technology Rationale
Feed Cache Redis Sorted Sets Pre-computed feed, O(log N) insert
Post Storage MySQL + Vitess ACID for posts, sharded by user_id
Fan-Out Queue Kafka Decouple write from delivery
Ranking ML Model (TensorFlow) Multi-signal personalized ranking
Media S3 + CloudFront CDN Scalable storage, global delivery
Social Graph TAO (Graph DB) / Neo4j Efficient friend/follower queries

Key Takeaways

Interview Tips

  • Clarify scope: Is it Twitter (public, follower-based) or Facebook (friends, bi-directional)?
  • Always discuss the push vs pull tradeoff -- and propose the hybrid approach
  • Feed ranking is a major differentiator: show you understand affinity, engagement, and time decay
  • Caching is critical: pre-computed feeds in Redis dramatically reduce read latency
  • Address the celebrity problem explicitly -- interviewers love this discussion point
  • Consider media delivery separately from text content (CDN, lazy loading)