Design a Social Media Feed (Twitter/X)
📖 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
1// ============================================2// Social Media Feed — Fan-Out Architecture3// ============================================45class FeedService {6 constructor(db, cache, queue) {7 this.db = db;8 this.cache = cache;9 this.queue = queue;10 this.CELEBRITY_THRESHOLD = 10000;11 }1213 async postTweet(userId, content) {14 // Save tweet15 const tweet = await this.db.insert('tweets', {16 id: this.generateId(), userId, content, createdAt: Date.now(),17 });1819 const followerCount = await this.db.getFollowerCount(userId);2021 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 read3233 return tweet;34 }3536 // Fan-out worker: pushes tweet to each follower's feed37 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 sorting44 JSON.stringify({ tweetId: event.tweetId, userId: event.userId, content: event.content })45 );46 // Trim to latest 800 tweets per feed47 await this.cache.zremrangebyrank(`feed:\${followerId}`, 0, -801);48 }49 }5051 // Get user's home feed52 async getFeed(userId, page = 0, pageSize = 20) {53 const start = page * pageSize;54 const end = start + pageSize - 1;5556 // Get pre-computed feed from cache57 let feedItems = await this.cache.zrevrange(58 `feed:\${userId}`, start, end59 );6061 // 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 timestamp68 feedItems = this.mergeAndSort(69 feedItems.map(item => JSON.parse(item)),70 celebrityTweets.flat()71 ).slice(0, pageSize);72 }7374 return feedItems;75 }7677 async getCelebrityFollowees(userId) {78 // Get followed users with > 10K followers79 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 }8485 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 }9394 mergeAndSort(feed1, feed2) {95 return [...feed1, ...feed2].sort((a, b) => b.createdAt - a.createdAt);96 }9798 generateId() { return Date.now().toString(36) + Math.random().toString(36).slice(2); }99}100101console.log("Feed system architecture demonstrated.");
🏋️ Practice Exercise
Full Feed Design: Design the complete feed system for Twitter: posting, following, feed generation, search, trending topics. Include database schema, caching, and message queues.
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?
Feed Ranking: Instead of chronological feed, design an algorithmic feed that ranks tweets by relevance (engagement, recency, user affinity). What signals do you use?
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)