Design a Social Media Feed (Twitter/X)

0/4 in this phase0/45 across the roadmap

📖 Concept

Designing a social media feed is one of the most asked system design questions. It tests your understanding of fan-out, caching, data models, and tradeoffs at massive scale.

Requirements

Functional: Post tweets, follow/unfollow users, view home timeline (feed of followed users' tweets), search tweets

Non-Functional: 300M MAU, average 200 followers per user, 500M tweets/day, feed must load in < 200ms

The Core Problem: Fan-Out

When a user tweets, how do followers see it in their feed?

Fan-Out on Write (Push Model)

When a user tweets, immediately push the tweet to all followers' feeds.

  • User A tweets → write to User B's feed, User C's feed, ... (all followers)
  • Reading feed = just read pre-computed feed (fast read, slow write)

Fan-Out on Read (Pull Model)

When a user views their feed, query all followed users' tweets in real-time.

  • User B opens feed → query User A's tweets, User C's tweets, ... merge and sort
  • Reading feed = expensive computation (slow read, fast write)

Hybrid Approach (Best)

  • Regular users (< 10K followers): Fan-out on write (push to followers' feeds)
  • Celebrities (> 10K followers): Fan-out on read (don't push to millions of feeds; pull when followers view their timeline)

This is exactly what Twitter does.

Data Model

  • Tweet: {id, userId, content, mediaURLs, createdAt}
  • Follow: {followerId, followeeId}
  • Feed: {userId, tweetId, createdAt} — pre-computed feed per user
  • User: {id, name, handle, followerCount, followingCount}

Interview tip: Always mention the hybrid fan-out approach. It shows you understand that one-size-fits-all solutions don't work at Twitter scale.

💻 Code Example

codeTap to expand ⛶
1// ============================================
2// Social Media Feed — Fan-Out Architecture
3// ============================================
4
5class FeedService {
6 constructor(db, cache, queue) {
7 this.db = db;
8 this.cache = cache;
9 this.queue = queue;
10 this.CELEBRITY_THRESHOLD = 10000;
11 }
12
13 async postTweet(userId, content) {
14 // Save tweet
15 const tweet = await this.db.insert('tweets', {
16 id: this.generateId(), userId, content, createdAt: Date.now(),
17 });
18
19 const followerCount = await this.db.getFollowerCount(userId);
20
21 if (followerCount < this.CELEBRITY_THRESHOLD) {
22 // Regular user: Fan-out on write (push to followers)
23 await this.queue.publish('fanout', {
24 type: 'push',
25 tweetId: tweet.id,
26 userId,
27 content,
28 createdAt: tweet.createdAt,
29 });
30 }
31 // Celebrity: Don't fan out — their tweets are pulled on read
32
33 return tweet;
34 }
35
36 // Fan-out worker: pushes tweet to each follower's feed
37 async processFanout(event) {
38 const followers = await this.db.getFollowers(event.userId);
39 for (const followerId of followers) {
40 // Add to follower's cached feed (Redis sorted set)
41 await this.cache.zadd(
42 `feed:\${followerId}`,
43 event.createdAt, // Score = timestamp for sorting
44 JSON.stringify({ tweetId: event.tweetId, userId: event.userId, content: event.content })
45 );
46 // Trim to latest 800 tweets per feed
47 await this.cache.zremrangebyrank(`feed:\${followerId}`, 0, -801);
48 }
49 }
50
51 // Get user's home feed
52 async getFeed(userId, page = 0, pageSize = 20) {
53 const start = page * pageSize;
54 const end = start + pageSize - 1;
55
56 // Get pre-computed feed from cache
57 let feedItems = await this.cache.zrevrange(
58 `feed:\${userId}`, start, end
59 );
60
61 // Merge celebrity tweets (fan-out on read)
62 const celebrities = await this.getCelebrityFollowees(userId);
63 if (celebrities.length > 0) {
64 const celebrityTweets = await Promise.all(
65 celebrities.map(celeb => this.getRecentTweets(celeb, pageSize))
66 );
67 // Merge and sort all tweets by timestamp
68 feedItems = this.mergeAndSort(
69 feedItems.map(item => JSON.parse(item)),
70 celebrityTweets.flat()
71 ).slice(0, pageSize);
72 }
73
74 return feedItems;
75 }
76
77 async getCelebrityFollowees(userId) {
78 // Get followed users with > 10K followers
79 return this.db.query(
80 'SELECT followee_id FROM follows WHERE follower_id = $1 AND followee_followers > $2',
81 [userId, this.CELEBRITY_THRESHOLD]
82 );
83 }
84
85 async getRecentTweets(userId, limit) {
86 const cached = await this.cache.get(`tweets:\${userId}`);
87 if (cached) return JSON.parse(cached);
88 return this.db.query(
89 'SELECT * FROM tweets WHERE user_id = $1 ORDER BY created_at DESC LIMIT $2',
90 [userId, limit]
91 );
92 }
93
94 mergeAndSort(feed1, feed2) {
95 return [...feed1, ...feed2].sort((a, b) => b.createdAt - a.createdAt);
96 }
97
98 generateId() { return Date.now().toString(36) + Math.random().toString(36).slice(2); }
99}
100
101console.log("Feed system architecture demonstrated.");

🏋️ Practice Exercise

  1. Full Feed Design: Design the complete feed system for Twitter: posting, following, feed generation, search, trending topics. Include database schema, caching, and message queues.

  2. Celebrity Problem: A user with 50M followers tweets. Design the fan-out strategy. How long until all followers see the tweet? What are the resource requirements?

  3. Feed Ranking: Instead of chronological feed, design an algorithmic feed that ranks tweets by relevance (engagement, recency, user affinity). What signals do you use?

  4. Real-time Feed Updates: When a user has their feed open and a new tweet arrives, how do you push it to the client in real-time? Design the WebSocket architecture.

⚠️ Common Mistakes

  • Using only fan-out on write — when a celebrity with 50M followers tweets, writing to 50M feeds takes too long and wastes storage. Use hybrid approach.

  • Not caching the feed — regenerating the feed from database on every request is too slow. Pre-compute and cache in Redis sorted sets.

  • Ignoring the delete/edit case — when a tweet is deleted, you must remove it from millions of cached feeds. This is expensive with fan-out on write.

💼 Interview Questions

🎤 Mock Interview

Practice a live interview for Design a Social Media Feed (Twitter/X)