LeetCode ยท #355

Design Twitter Solution

Design a simplified Twitter where users can post tweets, follow/unfollow other users, and retrieve their 10 most recent tweets in their news feed.

01
Section One ยท Problem

Problem Statement

๐Ÿท๏ธ

Difficulty

Medium

๐Ÿ”—

LeetCode

Problem #355

๐Ÿ—๏ธ

Pattern

Design โ€” merge k sorted feeds with heap

Implement the Twitter class with four methods: postTweet(userId, tweetId), getNewsFeed(userId) (returns most recent 10 tweets from the user and their followees), follow(followerId, followeeId), and unfollow(followerId, followeeId).

Example
Twitter twitter = new Twitter(); twitter.postTweet(1, 5); // User 1 posts tweet 5 twitter.getNewsFeed(1); // โ†’ [5] twitter.follow(1, 2); // User 1 follows user 2 twitter.postTweet(2, 6); // User 2 posts tweet 6 twitter.getNewsFeed(1); // โ†’ [6, 5] (most recent first) twitter.unfollow(1, 2); // User 1 unfollows user 2 twitter.getNewsFeed(1); // โ†’ [5] (user 2's tweets gone)
Constraints
โ€ข 1 โ‰ค userId, followerId, followeeId โ‰ค 500 โ€ข 0 โ‰ค tweetId โ‰ค 10โด โ€ข At most 3 ร— 10โด calls total
02
Section Two ยท Approach 1

Brute Force โ€” Collect & Sort

On getNewsFeed, collect all tweets from the user and their followees into one list, sort by timestamp, and return the top 10. Simple but expensive when users have many tweets.

Why it works

Sorting by timestamp guarantees recency. Taking the first 10 gives the correct feed.

Why we can do better
Problem: Collecting all tweets means iterating over potentially thousands of tweets just to pick 10. Since each user's tweets are already in chronological order, this is a merge k sorted lists problem โ€” a min-heap of size k (where k = number of followed users) merges them in O(10 ยท log k) instead of sorting everything.
Java โ€” Collect & Sort
// Pseudocode โ€” full brute force public List<Integer> getNewsFeed(int userId) { List<int[]> all = new ArrayList<>(); // add all tweets from user + followees with timestamps // sort all by timestamp descending // return first 10 tweet IDs }
Python โ€” Collect & Sort
def getNewsFeed(self, userId: int) -> list[int]: all_tweets = [] # collect tweets from user + followees with timestamps all_tweets.sort(key=lambda t: -t[0]) # sort by time desc return [t[1] for t in all_tweets[:10]]
MetricValue
Time (getNewsFeed)O(T log T) โ€” T = total tweets from user + followees
SpaceO(T)
03
Section Three ยท Approach 2

Heap Merge โ€” O(10 ยท log k)

Each user's tweet list is already sorted by time (newest first). To build a feed, treat each user's list as a sorted stream and merge them with a max-heap keyed by timestamp. Pop 10 times to get the 10 most recent across all followed users.

๐Ÿ’ก Mental model: Imagine k newspaper stands, each with papers stacked newest-on-top. You want today's top 10 headlines across all stands. Instead of collecting every paper and sorting, you peek at the top paper from each stand (put them in a heap). Pick the newest headline, then reveal the next paper from that stand. After 10 picks, you have your top-10 headlines without touching most papers.
Data Structures
  • userTweets: HashMap<userId, List<(time, tweetId)>> โ€” each user's tweets in chronological order.
  • followees: HashMap<userId, HashSet<followeeId>> โ€” who each user follows.
  • Global timestamp: Counter incremented with each tweet for ordering.
Algorithm โ€” getNewsFeed
  • Step 1: Collect the user's own ID + all followee IDs.
  • Step 2: For each person, if they have tweets, push their most recent tweet into a max-heap (keyed by timestamp).
  • Step 3: Pop from the heap up to 10 times. After each pop, push the next tweet from that same user (if any).
  • Step 4: Return the collected tweet IDs.
๐ŸŽฏ When to recognize this pattern:
  • Merging multiple sorted streams into one โ€” this is the exact same pattern as LC 23 (Merge k Sorted Lists).
  • The heap holds one element per stream and always yields the global best.
  • It appears in Twitter feeds, calendar event merging, and multi-way external sort.
Why only 10 pops?:
  • The problem caps the feed at 10 tweets. We never need to merge the full lists โ€” just pop 10 times from the heap.
  • This makes getNewsFeed O(10 ยท log k) = O(log k) regardless of how many tweets exist.
04
Section Four ยท Trace

Visual Walkthrough

Trace the example sequence:

Twitter operations โ€” follow graph + heap feed merge
postTweet(1, 5) User 1 tweets: [(t=0, id=5)] getNewsFeed(1) Follows: {self}. Heap: [(t=0,id=5)]. Pop โ†’ [5] follow(1, 2) User 1 now follows: {2} postTweet(2, 6) User 2 tweets: [(t=1, id=6)] getNewsFeed(1) โ€” heap merge Sources: User 1 [(t=0,5)], User 2 [(t=1,6)] Init heap: push most recent from each โ†’ [(t=1,6,user2), (t=0,5,user1)] Pop (t=1,6) โ†’ feed=[6]. User 2 has no more tweets. Pop (t=0,5) โ†’ feed=[6,5]. Done (only 2 tweets exist). โ†’ [6, 5] unfollow(1, 2) User 1 follows: {} (removed user 2) getNewsFeed(1) โ€” after unfollow Sources: only User 1 [(t=0,5)]. User 2 excluded. โ†’ [5] All operations correct โœ“
05
Section Five ยท Implementation

Code โ€” Java & Python

Java โ€” HashMap + Heap merge
import java.util.*; class Twitter { int time = 0; Map<Integer, List<int[]>> tweets; // userId โ†’ [(time, tweetId)] Map<Integer, Set<Integer>> following; // userId โ†’ set of followees public Twitter() { tweets = new HashMap<>(); following = new HashMap<>(); } public void postTweet(int userId, int tweetId) { tweets.computeIfAbsent(userId, k -> new ArrayList<>()) .add(new int[]{time++, tweetId}); } public List<Integer> getNewsFeed(int userId) { // Max-heap: [time, tweetId, userId, index in that user's list] PriorityQueue<int[]> heap = new PriorityQueue<>((a, b) -> b[0] - a[0]); Set<Integer> users = new HashSet<>(); users.add(userId); if (following.containsKey(userId)) users.addAll(following.get(userId)); for (int uid : users) { List<int[]> tws = tweets.get(uid); if (tws != null && !tws.isEmpty()) { int idx = tws.size() - 1; // most recent int[] tw = tws.get(idx); heap.offer(new int[]{tw[0], tw[1], uid, idx}); } } List<Integer> feed = new ArrayList<>(); while (!heap.isEmpty() && feed.size() < 10) { int[] top = heap.poll(); feed.add(top[1]); // tweetId int nextIdx = top[3] - 1; if (nextIdx >= 0) { int[] next = tweets.get(top[2]).get(nextIdx); heap.offer(new int[]{next[0], next[1], top[2], nextIdx}); } } return feed; } public void follow(int followerId, int followeeId) { following.computeIfAbsent(followerId, k -> new HashSet<>()) .add(followeeId); } public void unfollow(int followerId, int followeeId) { Set<Integer> s = following.get(followerId); if (s != null) s.remove(followeeId); } }
Python โ€” defaultdict + heapq merge
from collections import defaultdict import heapq class Twitter: def __init__(self): self.time = 0 self.tweets = defaultdict(list) # userId โ†’ [(time, tweetId)] self.following = defaultdict(set) # userId โ†’ set of followees def postTweet(self, userId: int, tweetId: int) -> None: self.tweets[userId].append((self.time, tweetId)) self.time += 1 def getNewsFeed(self, userId: int) -> list[int]: heap = [] # max-heap (negate time) users = self.following[userId] | {userId} for uid in users: if self.tweets[uid]: idx = len(self.tweets[uid]) - 1 t, tid = self.tweets[uid][idx] heapq.heappush(heap, (-t, tid, uid, idx)) feed = [] while heap and len(feed) < 10: neg_t, tid, uid, idx = heapq.heappop(heap) feed.append(tid) if idx > 0: t2, tid2 = self.tweets[uid][idx - 1] heapq.heappush(heap, (-t2, tid2, uid, idx - 1)) return feed def follow(self, followerId: int, followeeId: int) -> None: self.following[followerId].add(followeeId) def unfollow(self, followerId: int, followeeId: int) -> None: self.following[followerId].discard(followeeId)
06
Section Six ยท Analysis

Complexity Analysis

OperationTimeSpaceNotes
postTweetO(1)O(1)Append to list
follow / unfollowO(1)O(1)HashSet add/remove
getNewsFeed (brute)O(T log T)O(T)Collect all, sort
getNewsFeed (heap) โ† optimal O(k + 10 log k) O(k) k = followees. Init k, pop 10 times.
Overall space:
  • O(U + T + F) where U = users, T = total tweets, F = total follow relationships.
  • Each tweet is stored once; the heap is transient (created per getNewsFeed call).
07
Section Seven ยท Edge Cases

Edge Cases & Pitfalls

CaseScenarioWhy It Matters
No tweetsgetNewsFeed for new userHeap is empty โ†’ return empty list.
Self-followfollow(1, 1)User's own tweets should already appear. follow(self) should be harmless (idempotent).
Unfollow not followedunfollow(1, 99)Should not throw. Use discard or null-check.
More than 10 tweetsUser posts 20 tweetsFeed should return exactly 10 most recent.
Multiple users' tweets interleavedUsers post alternatelyHeap merge correctly interleaves by timestamp.
Follow then unfollowFeed should reflect current follow stategetNewsFeed rebuilds sources on each call โ€” no stale data.
โš  Common Mistake: Forgetting to include the user's own tweets in the feed. The user always sees their own tweets โ€” add userId to the set of sources before merging. Don't require self-follow; handle it implicitly.

โ† Back to Heap problems