Design a Social Media Feed: Instagram/Twitter System Design
System design walkthrough for a social media feed — fan-out strategies, feed ranking, the celebrity problem, caching, and infinite scroll pagination.
The social media feed is probably the most frequently asked system design question, and for good reason. It touches on the hardest tradeoff in distributed systems: do you do the work at write time or read time? That single decision shapes the entire architecture, and interviewers love watching how you reason through it.
Let's work through this like an actual interview.
Requirements
Functional:- Users can create posts (text, images, videos)
- Users can follow other users
- Users see a feed of posts from people they follow
- Likes and comments on posts
- Feed can be chronological or ranked
- 500M daily active users
- Average user follows 200 people
- Average user creates 2 posts per day → 1B new posts/day
- Feed must load in under 200ms
- High availability — feed is the core product
- Posts per second: 1B / 86,400 ≈ 12K writes/sec
- Feed reads: assume users check feed 10 times/day → 5B reads/day ≈ 58K reads/sec
- Read-heavy: ~5:1 read-to-write ratio
- Each post: ~1KB metadata + media URLs → 1TB/day of post metadata
The Core Design Decision: Fan-Out Strategy
This is the heart of the problem. When User A creates a post and has 1,000 followers, those 1,000 followers need to eventually see it in their feed. There are two fundamentally different approaches.
Fan-Out-on-Write (Push Model)
When a user creates a post, immediately push it to every follower's pre-computed feed.
Alice posts a photo (500 followers)
→ Write post to posts table
→ For each of Alice's 500 followers:
→ Insert post_id into their feed cache (Redis sorted set)
The data structure: Each user has a Redis sorted set where the score is the timestamp and the value is the post ID.
user:bob:feed = {
(post_id_1, timestamp_1),
(post_id_2, timestamp_2),
...
}
When Bob opens the app, reading the feed is just: ZREVRANGE user:bob:feed 0 19 — grab the 20 most recent post IDs, fetch the post content, done. Sub-millisecond.
- Reading is extremely fast — the feed is pre-computed
- Simple read path
- Predictable read latency
- Write amplification — one post by a celebrity with 10M followers means 10M write operations
- Wasted work — most followers won't check their feed before the next post pushes this one down
- Hot partitions — a celebrity posting creates a massive burst of writes
Fan-Out-on-Read (Pull Model)
When a user opens their feed, fetch posts from all users they follow at that moment.
Bob opens his feed (follows 200 users)
→ For each of Bob's 200 followees:
→ Fetch their latest posts
→ Merge and sort all posts by timestamp
→ Return top 20
Pros:
- Writes are cheap — just store the post once
- No wasted computation for inactive users
- Always up-to-date (no stale pre-computed feeds)
- Slow reads — fetching and merging posts from 200 sources takes time
- Read latency is unpredictable (depends on how many people you follow)
- Heavy load on the database for popular users
The Hybrid Approach (What Actually Works)
Here's what companies like Twitter actually figured out: neither pure approach works alone. The solution is a hybrid:
- Normal users (under ~10K followers): fan-out-on-write. The write amplification is manageable and reads are instant.
- Celebrities (over ~10K followers): fan-out-on-read. Don't pre-compute their posts into millions of feeds. Instead, when a user opens their feed, merge the pre-computed feed (from normal followees) with fresh posts from celebrity followees.
Bob opens feed:
- Read pre-computed feed from Redis (posts from normal followees) → fast
- Fetch latest posts from celebrity followees (maybe 3-5 celebrities) → a few queries
- Merge and sort
- Return top 20
This hybrid gives you the read speed of fan-out-on-write for the 99% case while avoiding the write explosion for celebrities. The threshold (10K? 100K?) is tunable.
High-Level Architecture
┌──────────┐ ┌──────────────┐
│ Client ├────────►│ API Gateway │
└──────────┘ └──────┬───────┘
│
┌─────────────┼─────────────┐
│ │ │
┌─────▼────┐ ┌────▼─────┐ ┌───▼──────┐
│Post │ │Feed │ │User │
│Service │ │Service │ │Service │
└─────┬────┘ └────┬─────┘ └───┬──────┘
│ │ │
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│Post DB │ │Feed Cache│ │User DB │
│(MySQL) │ │(Redis) │ │(MySQL) │
└──────────┘ └──────────┘ └──────────┘
│
▼
┌──────────┐ ┌──────────┐
│Fan-out │──────►│ Kafka │
│Service │ └──────────┘
└──────────┘
Post creation flow:
- Client uploads media to S3/CDN, gets media URLs
POST /postswith content + media URLs- Post Service writes to Post DB
- Post Service sends event to Fan-out Service via Kafka
- Fan-out Service checks follower count:
GET /feed?cursor=timestamp_123- Feed Service reads user's pre-computed feed from Redis
- Feed Service fetches posts from celebrity followees
- Merge, rank, return top N posts with next cursor
Post Storage
CREATE TABLE posts (
post_id BIGINT PRIMARY KEY,
user_id BIGINT NOT NULL,
content TEXT,
media_urls JSON,
like_count INT DEFAULT 0,
comment_count INT DEFAULT 0,
created_at TIMESTAMP,
INDEX idx_user_created (user_id, created_at DESC)
);
CREATE TABLE follows (
follower_id BIGINT,
followee_id BIGINT,
created_at TIMESTAMP,
PRIMARY KEY (follower_id, followee_id),
INDEX idx_followee (followee_id)
);
Media (images, videos) is stored in S3. The post table only stores URLs. A CDN sits in front of S3 for serving media.
Feed Ranking
A chronological feed is simple — just sort by timestamp. But every major platform uses ranked feeds now because they increase engagement.
Ranking signals:- Recency (newer = higher base score)
- Relationship strength (how often you interact with this person)
- Post engagement (likes, comments, shares in early minutes)
- Content type (video might get a boost, or not — depends on platform goals)
- Diversity (don't show 10 posts from the same person in a row)
Raw feed (chronological, 500 posts)
→ Ranking Service (ML model scores each post)
→ Top 20 posts returned to client
For an interview, you don't need to design the ML model. Just mention that ranking is a separate service that re-orders pre-fetched candidates, and that it runs on features like recency, engagement, and social graph proximity.
Infinite Scroll: Cursor-Based Pagination
Let's be honest — offset-based pagination (LIMIT 20 OFFSET 40) breaks for feeds. New posts push everything down, causing duplicates or missed content.
First request: GET /feed?limit=20
Response: { posts: [...], nextCursor: "2026-03-27T10:30:00Z" }
Next request: GET /feed?limit=20&cursor=2026-03-27T10:30:00Z
(fetch 20 posts older than cursor)
This is stable even when new posts are added. The cursor acts as a bookmark — "show me things older than this." No items are skipped or duplicated.
For the Redis sorted set, this translates to: ZREVRANGEBYSCORE user:bob:feed +inf cursor_timestamp LIMIT 0 20
Caching Strategy
Hot feeds (active users): Keep in Redis. The sorted set per user is the pre-computed feed. Size: ~2000 post IDs per user × 8 bytes each = ~16KB per user. For 100M active users: ~1.6 TB in Redis. Large but feasible with a Redis cluster. Cold feeds (inactive users): Don't waste memory caching feeds for users who haven't logged in for weeks. When they return, compute their feed on demand (fan-out-on-read for the initial load), then start maintaining their cache again. Post content cache: The feed cache stores only post IDs. Post content is cached separately. The top 10% of posts (by view count) are cached in Redis to avoid hitting the database for popular content. Cache invalidation: When a post is deleted, remove it from the author's followers' feed caches. This is expensive (another fan-out), but deletes are rare enough that it's manageable. When a user unfollows someone, lazily filter out that person's posts on the next feed read rather than immediately cleaning the cache.Handling Likes and Comments
Likes and comments are high-frequency operations. At scale, a popular post might get thousands of likes per second.
Like counts: Don't update the database on every like. Buffer likes in Redis (INCR post:123:likes) and flush to the database periodically. The client sees the Redis count, which is always fresh.
Comments: Stored in a separate table, partitioned by post_id. Fetched separately when a user taps into a post. Don't load comments with the feed — that would be wasteful since most users scroll past without reading comments.
Notifications
When someone likes your post or comments on it, you want a notification. But you don't want to notify a user about every single like if their post is going viral.
Batching: "Alice and 47 others liked your post" instead of 48 individual notifications. Priority: Direct comments → mentions → likes. Throttle low-priority notifications. Delivery: Push notifications via FCM/APNs for mobile, WebSocket for web clients who are active, email digest for inactive users.What Interviewers Are Looking For
The feed design question tests three things:
- Can you articulate the fan-out tradeoff? This is non-negotiable. If you don't discuss push vs pull vs hybrid, you haven't answered the question.
- Do you understand the data model? Redis sorted sets for feeds, SQL for posts and relationships, S3 for media. Each choice should be justified.
- Can you handle follow-up questions? "What happens when a celebrity with 50M followers posts?" "How do you handle a user who follows 10,000 people?" "What if a post goes viral?" These test whether you really understand the tradeoffs or just memorized an architecture.
Sharpen your system design skills by working through these problems with feedback at CodeUp — understanding the why behind each decision matters more than memorizing the architecture.