System DesignAdvanced9 min readPart 13 of 14

News Feed

Designing a news feed like Facebook, Twitter or Instagram tests your understanding of fan-out, caching and real-time data delivery. The core challenge: when someone posts, how do you get it to millions of followers efficiently?

Can you explain news feed out loud?

I'm Alex. Read this, then I'll interview you on a design problem and push on the parts you skip.

Clarify the requirementsCore idea

Functional

Post text, images and video. Follow and unfollow. See a personal feed of posts from people you follow. Like, comment and share.

Non-functional

500M daily active users and 1M posts a minute. The feed loads in under 200ms. Eventual consistency is fine: a feed can be a few seconds behind.

High-level architectureDesign

%%{init: {"look":"handDrawn","handDrawnSeed":7,"theme":"base","fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","themeVariables":{"fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","fontSize":"16px","primaryColor":"#fff4e6","primaryBorderColor":"#1e1e1e","primaryTextColor":"#1e1e1e","secondaryColor":"#e7f5ff","tertiaryColor":"#ebfbee","lineColor":"#1e1e1e","textColor":"#1e1e1e","mainBkg":"#fff4e6","nodeBorder":"#1e1e1e","clusterBkg":"#f8f9fa","edgeLabelBackground":"#ffffff","actorBkg":"#e7f5ff","actorBorder":"#1e1e1e","noteBkgColor":"#fff9db","noteBorderColor":"#1e1e1e"}}}%%
graph LR
  Client["Client"] -->|"1. create post"| LB["Load balancer"]
  Client -->|"5. load feed"| LB
  LB --> PS["Post service"]
  LB --> FS["Feed service"]
  PS -->|"2. store"| PDB[("Post DB<br/>Cassandra")]
  PS -->|"3. publish"| Q["Kafka"]
  Q --> FO["Fan-out service"]
  FO --> UG[("User graph<br/>Redis")]
  FO -->|"4. push to followers"| FC["Feed cache<br/>Redis"]
  FS -->|"6. read"| FC
  class Client blue
  class LB,PS,FS orange
  class Q yellow
  class FO green
  class FC,UG red
  class PDB violet
classDef orange fill:#ffd8a8,stroke:#1e1e1e,color:#1e1e1e
classDef blue fill:#a5d8ff,stroke:#1e1e1e,color:#1e1e1e
classDef green fill:#b2f2bb,stroke:#1e1e1e,color:#1e1e1e
classDef red fill:#ffc9c9,stroke:#1e1e1e,color:#1e1e1e
classDef violet fill:#d0bfff,stroke:#1e1e1e,color:#1e1e1e
classDef yellow fill:#ffec99,stroke:#1e1e1e,color:#1e1e1e
classDef grey fill:#e9ecef,stroke:#1e1e1e,color:#1e1e1e
DiagramPosting and reading are separate paths. The fan-out service connects them through Kafka.

Post service

Creates, validates and stores posts. Stateless, so it scales out.

Feed service

Serves the personal feed from the Redis cache, 50 posts per request.

Fan-out service

The core. Reads post events from Kafka and adds the post ID to each follower’s feed cache, separately from posting.

Feed cache (Redis)

A sorted set per user, scored by time, holding the top 500 post IDs for instant reads.

User graph

Who follows whom: a Redis set per user, followers:{userId}.

Request flowsHow it works

Posting and fan-out

%%{init: {"look":"handDrawn","handDrawnSeed":7,"theme":"base","fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","themeVariables":{"fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","fontSize":"16px","primaryColor":"#fff4e6","primaryBorderColor":"#1e1e1e","primaryTextColor":"#1e1e1e","secondaryColor":"#e7f5ff","tertiaryColor":"#ebfbee","lineColor":"#1e1e1e","textColor":"#1e1e1e","mainBkg":"#fff4e6","nodeBorder":"#1e1e1e","clusterBkg":"#f8f9fa","edgeLabelBackground":"#ffffff","actorBkg":"#e7f5ff","actorBorder":"#1e1e1e","noteBkgColor":"#fff9db","noteBorderColor":"#1e1e1e"}}}%%
sequenceDiagram
  participant A as User A
  participant PS as Post service
  participant DB as Cassandra
  participant Q as Kafka
  participant FO as Fan-out
  participant Cache as Feed cache
  A->>PS: create post
  PS->>DB: store post
  PS->>Q: publish post-created
  PS-->>A: 201 created
  Q->>FO: post event
  FO->>FO: load A's followers
  FO->>Cache: ZADD feed:B post_id time
  FO->>Cache: ZADD feed:C post_id time
  Note over FO,Cache: B and C now see the post
SequenceThe author gets “201 created” straight away; fan-out happens in the background.

Loading the feed

%%{init: {"look":"handDrawn","handDrawnSeed":7,"theme":"base","fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","themeVariables":{"fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","fontSize":"16px","primaryColor":"#fff4e6","primaryBorderColor":"#1e1e1e","primaryTextColor":"#1e1e1e","secondaryColor":"#e7f5ff","tertiaryColor":"#ebfbee","lineColor":"#1e1e1e","textColor":"#1e1e1e","mainBkg":"#fff4e6","nodeBorder":"#1e1e1e","clusterBkg":"#f8f9fa","edgeLabelBackground":"#ffffff","actorBkg":"#e7f5ff","actorBorder":"#1e1e1e","noteBkgColor":"#fff9db","noteBorderColor":"#1e1e1e"}}}%%
sequenceDiagram
  participant B as User B
  participant FS as Feed service
  participant Cache as Feed cache
  participant DB as Cassandra
  B->>FS: GET /feed?cursor=abc
  FS->>Cache: ZREVRANGE feed:B 0 49
  alt cache hit
    Cache-->>FS: top 50 post IDs
  else cache miss
    FS->>DB: build B's feed from the DB
    DB-->>FS: posts
  end
  FS->>DB: batch-fetch post details
  DB-->>FS: post contents
  FS-->>B: feed
SequencePost IDs come from the cache; post contents are fetched in one batch.

Deep dive: fan-out strategiesPattern

This is the central design decision. There are two basic approaches, and the real answer combines them.

Fan-out on write (push)fast reads

When someone posts, push it into every follower’s feed cache right away. Reading a feed is then one cache lookup. The problem: a celebrity with 500M followers would need 500M cache writes for a single post.

Fan-out on read (pull)cheap writes

When someone opens their feed, fetch recent posts from everyone they follow and merge them. No write storms, but a feed load can mean hundreds of database queries.

%%{init: {"look":"handDrawn","handDrawnSeed":7,"theme":"base","fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","themeVariables":{"fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","fontSize":"16px","primaryColor":"#fff4e6","primaryBorderColor":"#1e1e1e","primaryTextColor":"#1e1e1e","secondaryColor":"#e7f5ff","tertiaryColor":"#ebfbee","lineColor":"#1e1e1e","textColor":"#1e1e1e","mainBkg":"#fff4e6","nodeBorder":"#1e1e1e","clusterBkg":"#f8f9fa","edgeLabelBackground":"#ffffff","actorBkg":"#e7f5ff","actorBorder":"#1e1e1e","noteBkgColor":"#fff9db","noteBorderColor":"#1e1e1e"}}}%%
graph TD
  P["user creates a post"] --> D{"how many<br/>followers?"}
  D -->|"~99% of users<br/>(under ~1000)"| Push["fan-out on write<br/>push to follower caches"]
  D -->|"~1%: celebrities"| Pull["fan-out on read<br/>fetch at load time"]
  Push --> M["merge when the feed loads"]
  Pull --> M
  class P blue
  class D yellow
  class Push green
  class Pull orange
  class M violet
classDef orange fill:#ffd8a8,stroke:#1e1e1e,color:#1e1e1e
classDef blue fill:#a5d8ff,stroke:#1e1e1e,color:#1e1e1e
classDef green fill:#b2f2bb,stroke:#1e1e1e,color:#1e1e1e
classDef red fill:#ffc9c9,stroke:#1e1e1e,color:#1e1e1e
classDef violet fill:#d0bfff,stroke:#1e1e1e,color:#1e1e1e
classDef yellow fill:#ffec99,stroke:#1e1e1e,color:#1e1e1e
classDef grey fill:#e9ecef,stroke:#1e1e1e,color:#1e1e1e
DiagramThe hybrid Facebook uses: push for most users, pull for the few with huge followings, merged at read time.

Deep dive: feed rankingDesign

A purely chronological feed is simple but bad for engagement. Modern feeds rank posts using machine learning, based on signals like these:

Affinity

How often you interact with this person: likes, comments, profile visits.

Time decay

Newer posts rank higher: 2 hours old beats 2 days old.

Content type

Video ranks above images, images above links.

Engagement

Posts with many likes and comments get a boost.

A simplified ranking scoreText
score = affinity × 0.4 + engagement × 0.3 + time_decay × 0.2 + content_type × 0.1

Deep dive: cachingScale

Each user's feed is a Redis sorted set: post IDs ordered by a score (here, the timestamp).

Feed cache commandsText
# Key: feed:{userId}   Value: post IDs, score = timestamp
ZADD feed:user123 1725000000 post_abc
ZADD feed:user123 1725000060 post_def

# Latest 50 posts
ZREVRANGE feed:user123 0 49

Keep the top 500 posts per user in Redis; past that, fetch from the database. That gives sub-100ms feed loads for about 99% of sessions.

Cache sizingValue
Memory500M users × 500 posts × 8 bytes ≈ 2 TB
LayoutRedis Cluster, sharded across nodes, so it fits in memory
TTL7 days: old posts age out of the cache

Common interview mistakesPitfalls

Ignoring celebrity fan-outOne celebrity post can overwhelm a push-only design. Always discuss the hybrid.
Not caching feedsBuilding every feed from the database is too slow at this scale.
Over-rankingToo aggressive, and users stop seeing their friends’ posts.
No paginationNever return the whole feed. Use cursor-based pagination for infinite scroll.
Forgetting unfollowWhen A unfollows B, remove B’s posts from A’s feed cache.

Summary: key decisionsSummary

DecisionRecommendationWhy
Fan-outHybrid: push, plus pull for celebritiesHandles celebrities without write storms
Feed cacheRedis sorted setsFast, ordered reads by time
Post storageCassandraBuilt for appends, partitioned by user
RankingML model (affinity + time + engagement)Maximises engagement

Frequently asked questionsFAQ

What is the difference between push and pull models for a news feed?

Push, or fan-out on write, pre-computes each follower's feed when a post is created, delivering fast reads but heavy write amplification for users with many followers. Pull builds the feed at read time by merging recent posts, giving cheap writes but slower, more expensive reads.

How do you rank posts instead of just showing them chronologically?

Compute a relevance score per post from recency, interaction volume, relationship strength, and content type, then serve the top results. Because ranking features shift constantly, score where it is cheap, write-time for hot users and read-time otherwise, and cache the computed feeds.

How do you handle celebrity accounts in a feed system?

Use hybrid fan-out. Proactively push posts out to ordinary followers, but hold celebrity posts for read-time merging or enqueue their fan-out asynchronously so writes stay fast. That avoids exploding write amplification on a single celebrity post while keeping the read path quick for the vast majority of users.

Now explain news feed to me out loud

I'll give you a design problem and push on the trade-offs, like a real interviewer.

Practice with Alex

Keep readingKeep reading

Ready to practice?

Reading is step one. Saying it out loud is step two.

Start a mock interview with Alex, get pushed on the parts you skip, and see your hiring signal right after.