LLD Hub
lldobserverfan-outstrategy

Social Media Feed Low Level Design (Twitter/Instagram) — LLD Guide

Design a Twitter or Instagram feed with posts, follows, fan-out strategies, trending hashtags, and notification system. Advanced LLD for senior SDE rounds.

15 April 2025·9 min read

Practice this problem

Social Media Feed (Twitter/Instagram) — get AI-scored feedback on your solution

Solve it →

Social Media Feed (Twitter / Instagram) Low Level Design is an advanced problem asked in senior SDE interviews at Meta, Twitter, LinkedIn, and ShareChat. It covers post creation, follow relationships, feed generation (fan-out strategies), trending hashtags, and the Observer pattern for real-time updates. This guide covers the complete Social Media Feed LLD with Java code, class diagram, and FAQ.

Why Interviewers Ask Social Media Feed LLD

The feed problem is a design pattern showcase with a real algorithmic challenge. Interviewers want to see:

  • Can you explain fan-out on write vs fan-out on read for feed generation?
  • Do you use Observer pattern for real-time notification on new posts?
  • Can you model the follow relationship without a social graph framework?
  • Do you handle celebrity accounts differently (hybrid fan-out)?
  • Can you design trending hashtags using a sliding window counter?

Functional Requirements

  • Users can create posts (text, image, video) with optional hashtags
  • Users can follow and unfollow other users
  • Home feed shows recent posts from followed users, reverse chronological
  • Users can like and comment on posts
  • Trending hashtags — top 10 most used in the last hour
  • User profile feed — all posts by a specific user
  • Followers get a real-time notification when someone they follow posts

Non-Functional Requirements

  • Feed load must be under 200ms for a user with 1000 followees
  • A post by a celebrity (10M followers) must propagate to feeds within 60 seconds
  • Trending hashtags must update in near real-time (5-minute lag acceptable)
  • Feed must handle paginated reads (cursor-based, 20 posts per page)

Core Entities — Social Media Feed LLD Class Design

  • User — id, username, displayName, followerCount, followeeCount
  • Post — id, authorId, content, mediaUrls, hashtags, likeCount, createdAt
  • Follow — followerId, followeeId, createdAt
  • FeedItem — userId, postId, score (for ranking), addedAt
  • Like — userId, postId, createdAt
  • Comment — id, postId, authorId, content, createdAt
  • HashtagCount — hashtag, window, count (for trending)
  • FeedService — getFeed, addToFeeds (fan-out)
  • TrendingService — count hashtag use, return top-N

Text-Based Class Diagram

User
+-- id, username, bio: String
+-- followerCount, followeeCount: int
+-- isVerified: boolean

Post
+-- id, authorId: String
+-- content: String
+-- mediaUrls: List<String>
+-- hashtags: List<String>
+-- likeCount, commentCount: int
+-- createdAt: LocalDateTime

Follow
+-- followerId, followeeId: String
+-- createdAt: LocalDateTime

FeedItem
+-- userId, postId: String
+-- score: double  (for ranking)
+-- addedAt: LocalDateTime

FeedService
+-- getFeed(userId, cursor, limit): List<Post>
+-- onNewPost(post): void  // fan-out

TrendingService
+-- recordHashtags(hashtags): void
+-- getTopN(n, windowMinutes): List<String>

PostService
+-- createPost(request, userId): Post
+-- likePost(postId, userId): void
+-- addComment(postId, userId, content): Comment

Fan-Out on Write — Feed Generation

Fan-out on write: when a user posts, immediately push the post to all their followers' feed caches. Fast reads (O(1) cache hit), but expensive writes for celebrity accounts with millions of followers.

public class FeedService {
    private final FollowRepository followRepo;
    private final FeedCacheService feedCache; // Redis sorted set per user
    private static final int MAX_FEED_SIZE = 1000; // trim to 1000 posts per user
    private static final int CELEBRITY_THRESHOLD = 100_000; // followers

    public void onNewPost(Post post) {
        User author = userRepo.findById(post.getAuthorId());

        if (author.getFollowerCount() < CELEBRITY_THRESHOLD) {
            // Fan-out on write: push to all followers' feeds
            fanOutToFollowers(post);
        } else {
            // Celebrity: skip fan-out, let followers pull on read
            // Store post in celebrity's own feed only
            feedCache.addPost(post.getAuthorId(), post.getId(), post.getCreatedAt().toEpochSecond(ZoneOffset.UTC));
        }
    }

    private void fanOutToFollowers(Post post) {
        List<String> followerIds = followRepo.getFollowerIds(post.getAuthorId());
        double score = post.getCreatedAt().toEpochSecond(ZoneOffset.UTC);

        // Batch in chunks to avoid blocking
        Lists.partition(followerIds, 500).forEach(batch -> {
            for (String followerId : batch) {
                feedCache.addPost(followerId, post.getId(), score);
                feedCache.trim(followerId, MAX_FEED_SIZE); // keep feed bounded
            }
        });
    }

    // Hybrid read for celebrity accounts
    public List<Post> getFeed(String userId, String cursor, int limit) {
        // Get cached feed (fan-out posts)
        List<String> postIds = feedCache.getPostIds(userId, cursor, limit);

        // Merge in celebrity posts not fan-outed
        List<String> followedCelebs = followRepo.getFollowedCelebrities(userId);
        List<Post> celebPosts = followedCelebs.stream()
            .flatMap(celebId -> feedCache.getPostIds(celebId, cursor, limit * 2).stream())
            .map(postRepo::findById)
            .collect(Collectors.toList());

        // Merge and sort by score (timestamp)
        List<Post> allPosts = new ArrayList<>(postRepo.findAllByIds(postIds));
        allPosts.addAll(celebPosts);
        allPosts.sort(Comparator.comparing(Post::getCreatedAt).reversed());
        return allPosts.subList(0, Math.min(limit, allPosts.size()));
    }
}

Trending Hashtags — Sliding Window Counter

public class TrendingService {
    // hashtag -> sorted set of timestamps (for sliding window)
    private final ConcurrentHashMap<String, ConcurrentLinkedDeque<Long>> tagTimestamps = new ConcurrentHashMap<>();

    public void recordHashtags(List<String> hashtags) {
        long now = System.currentTimeMillis();
        for (String tag : hashtags) {
            tagTimestamps.computeIfAbsent(tag, k -> new ConcurrentLinkedDeque<>()).addLast(now);
        }
    }

    public List<String> getTopN(int n, int windowMinutes) {
        long cutoff = System.currentTimeMillis() - windowMinutes * 60_000L;

        return tagTimestamps.entrySet().stream()
            .collect(Collectors.toMap(
                Map.Entry::getKey,
                e -> {
                    // Count entries within window (evict expired entries)
                    e.getValue().removeIf(t -> t < cutoff);
                    return e.getValue().size();
                }
            ))
            .entrySet().stream()
            .sorted(Map.Entry.<String, Integer>comparingByValue().reversed())
            .limit(n)
            .map(Map.Entry::getKey)
            .collect(Collectors.toList());
    }
}

Key Design Decisions

  • Fan-out on write for normal users: Pre-compute feeds at write time so reads are O(1) cache hits. Works well for users with under 100K followers. The write overhead is acceptable for typical social graphs.
  • Hybrid fan-out for celebrities: Fan-out to 10M feeds synchronously is too slow. For celebrity posts, skip fan-out. On feed read, merge the user's cached feed with recent posts from any celebrities they follow. This is the exact strategy used by Twitter.
  • Redis sorted set with timestamp as score: Feed items are stored in a sorted set with Unix timestamp as the score. getFeed is a ZREVRANGE (reverse chronological) with cursor- based pagination — no full scan.
  • Sliding window for trending: A deque of timestamps per hashtag. On query, evict entries older than the window and count the remainder. This avoids storing a separate counter that could drift under concurrent increments.

Common Follow-Up Questions

  • "How do you rank feed posts beyond reverse chronological?" — Add an engagement score to FeedItem: likeCount * weightLike + commentCount * weightComment + recency * weightTime. Store score as the sorted set score in Redis. Update on likes and comments.
  • "How do you handle a user unfollowing someone?" — Remove all posts by that user from the unfollower's feed cache lazily (on next feed load, filter out unfollowed users) or eagerly (background job removes their postIds). Lazy is simpler; eager keeps the cache clean.
  • "How do you implement pagination for the feed?" — Use cursor-based pagination: the cursor is the score (timestamp) of the last seen item. ZREVRANGEBYSCORE in Redis returns items with score less than the cursor. No OFFSET — O(log n) per page.

FAQ — Social Media Feed Low Level Design

What is fan-out on write vs fan-out on read?

Fan-out on write: push a new post to all followers' feed caches at write time. Fast reads, expensive writes. Fan-out on read: do not push; when a user requests their feed, query all followees and merge. Slow reads (N queries for N followees), cheap writes. Hybrid: fan-out on write for regular users, fan-out on read for celebrity followees only.

How do you design trending hashtags?

Maintain a sliding window counter per hashtag. On post creation, record a timestamp for each hashtag. On trending query, count timestamps within the last N minutes. Sort by count descending and return top 10. For scale, use Redis with EXPIRE keys or a stream-processing framework like Kafka Streams for real-time aggregation.

What design patterns are used in Social Media Feed LLD?

The primary patterns are Observer (followers notified on new post),Strategy (fan-out strategy — write-through vs read-time merge for celebrities), and Repository (FeedCacheService backed by Redis, PostRepository backed by DB).

How do you handle a user with 10 million followers posting?

Skip the synchronous fan-out. Store the post in the author's own feed only. When any follower loads their feed, merge the cached fan-out feed with recent posts from followed celebrities. This hybrid approach (used by Twitter's Flock system) keeps both write latency and read latency bounded.

Ready to practice?

Submit your solution and get AI-scored feedback on OOP, SOLID principles, design patterns, and code quality.

Solve Social Media Feed (Twitter/Instagram)