March 27, 20269 min read

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.

system-design news-feed twitter instagram interviews
Ad 336x280

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
Non-functional:
  • 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
Quick estimation:
  • 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.

Pros:
  • Reading is extremely fast — the feed is pre-computed
  • Simple read path
  • Predictable read latency
Cons:
  • 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)
Cons:
  • 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:
  1. Read pre-computed feed from Redis (posts from normal followees) → fast
  2. Fetch latest posts from celebrity followees (maybe 3-5 celebrities) → a few queries
  3. Merge and sort
  4. 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:
  1. Client uploads media to S3/CDN, gets media URLs
  2. POST /posts with content + media URLs
  3. Post Service writes to Post DB
  4. Post Service sends event to Fan-out Service via Kafka
  5. Fan-out Service checks follower count:
- Under threshold: push post ID to each follower's Redis feed - Over threshold: skip (handled at read time) Feed read flow:
  1. GET /feed?cursor=timestamp_123
  2. Feed Service reads user's pre-computed feed from Redis
  3. Feed Service fetches posts from celebrity followees
  4. 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)
Implementation: A ranking service takes the raw feed (top ~500 candidates) and re-orders them using a lightweight ML model. The model predicts P(engagement) for each post given the viewing user.
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.

Cursor-based pagination uses the last item's timestamp (or ID) as a cursor:
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:

  1. 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.
  1. Do you understand the data model? Redis sorted sets for feeds, SQL for posts and relationships, S3 for media. Each choice should be justified.
  1. 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.
The hybrid fan-out approach is the key insight. Everything else — caching, pagination, ranking — is important but secondary to that core design decision.

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.

Ad 728x90