Skip to content

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#

POST /v1/posts
GET  /v1/feed?cursor=...&limit=20
POST /v1/follow/{user}

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.

  • 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.

Video walkthrough

Design Scalable News Feed System : via ByteByteGo