355. Design Twitter - Explanation

Problem Link

Description

Implement a simplified version of Twitter which allows users to post tweets, follow/unfollow each other, and view the 10 most recent tweets within their own news feed.

Users and tweets are uniquely identified by their IDs (integers).

Implement the following methods:

  • Twitter() Initializes the twitter object.
  • void postTweet(int userId, int tweetId) Publish a new tweet with ID tweetId by the user userId. You may assume that each tweetId is unique.
  • List<Integer> getNewsFeed(int userId) Fetches at most the 10 most recent tweet IDs in the user's news feed. Each item must be posted by users who the user is following or by the user themself. Tweets IDs should be ordered from most recent to least recent.
  • void follow(int followerId, int followeeId) The user with ID followerId follows the user with ID followeeId.
  • void unfollow(int followerId, int followeeId) The user with ID followerId unfollows the user with ID followeeId.

Example 1:

Input:
["Twitter", "postTweet", [1, 10], "postTweet", [2, 20], "getNewsFeed", [1], "getNewsFeed", [2], "follow", [1, 2], "getNewsFeed", [1], "getNewsFeed", [2], "unfollow", [1, 2], "getNewsFeed", [1]]

Output:
[null, null, null, [10], [20], null, [20, 10], [20], null, [10]]

Explanation:
Twitter twitter = new Twitter();
twitter.postTweet(1, 10); // User 1 posts a new tweet with id = 10.
twitter.postTweet(2, 20); // User 2 posts a new tweet with id = 20.
twitter.getNewsFeed(1);   // User 1's news feed should only contain their own tweets -> [10].
twitter.getNewsFeed(2);   // User 2's news feed should only contain their own tweets -> [20].
twitter.follow(1, 2);     // User 1 follows user 2.
twitter.getNewsFeed(1);   // User 1's news feed should contain both tweets from user 1 and user 2 -> [20, 10].
twitter.getNewsFeed(2);   // User 2's news feed should still only contain their own tweets -> [20].
twitter.unfollow(1, 2);   // User 1 unfollows user 2.
twitter.getNewsFeed(1);   // User 1's news feed should only contain their own tweets -> [10].

Constraints:

  • 1 <= userId, followerId, followeeId <= 100
  • 0 <= tweetId <= 1000


Topics

Recommended Time & Space Complexity

You should aim for a solution with O(nlogn) time for each getNewsFeed() function call, O(1) time for the remaining methods, and O((N * m) + (N * M) + n) space, where n is the number of followeeIds associated with the userId, m is the maximum number of tweets by any user, N is the total number of userIds, and M is the maximum number of followees for any user.


Hint 1

Can you think of a data structure to store all the information, such as userIds and corresponding followeeIds, or userIds and their tweets? Maybe you should think of a hash data structure in terms of key-value pairs. Also, can you think of a way to determine that a tweet was posted before another tweet?


Hint 2

We use a hash map followMap to store userIds and their unique followeeIds as a hash set. Another hash map, tweetMap, stores userIds and their tweets as a list of (count, tweetId) pairs. A counter count, incremented with each tweet, tracks the order of tweets. The variable count is helpful in distinguishing the time of tweets from two users. This way of storing data makes the functions follow(), unfollow(), and postTweet() run in O(1). Can you think of a way to implement getNewsFeed()? Maybe consider a brute force approach and try to optimize it.


Hint 3

A naive solution would involve taking the tweets of the userId and its followeeIds into a list, sorting them in descending order based on their count values, and returning the top 10 tweets as the most recent ones. Can you think of a more efficient approach that avoids collecting all tweets and sorting? Perhaps consider a data structure and leverage the fact that each user's individual tweets list is already sorted.


Hint 4

We can use a Max-Heap to efficiently retrieve the top 10 most recent tweets. For each followee and the userId, we insert their most recent tweet from the tweetMap into the heap, along with the tweet's count and its index in the tweet list. This index is necessary because after processing a tweet, we can insert the next most recent tweet from the same user's list. By always pushing and popping tweets from the heap, we ensure that the 10 most recent tweets are retrieved without sorting all tweets.


Company Tags

Please upgrade to NeetCode Pro to view company tags.



Prerequisites

Before attempting this problem, you should be comfortable with:

  • Hash Maps - Mapping user IDs to their tweets and follow relationships
  • Hash Sets - Tracking followee relationships with O(1) add/remove/lookup
  • Heap / Priority Queue - Merging k sorted lists efficiently to find top-k elements
  • Sorting - Custom comparators for ordering tweets by timestamp

1. Sorting

Intuition

Each user has their own tweets, and they also see tweets from all the people they follow.
To build the news feed:

  1. Collect all tweets from the user.
  2. Collect all tweets from every followee.
  3. Combine these tweets into one list.
  4. Sort them by timestamp (most recent first).
  5. Return the first 10 tweet IDs.

This works because sorting guarantees we always pick the latest 10 tweets, regardless of who posted them.

Algorithm

  1. Maintain:

    • tweetMap[user] -> list of (time, tweetId)
    • followMap[user] -> set of users they follow
    • time -> increases every time someone posts a tweet
  2. postTweet(user, tweetId)

    • Store (current_time, tweetId) in that user's list
    • Increment global time
  3. getNewsFeed(user)

    • Start with the user's tweets
    • Add tweets from all followees
    • Sort the combined list by time descending
    • Return first 10 tweet IDs
  4. follow(follower, followee)

    • Add followee to follower's follow-set (ignore if same user)
  5. unfollow(follower, followee)

    • Remove followee from follow-set if present
class Twitter:

    def __init__(self):
        self.time = 0
        self.followMap = defaultdict(set)
        self.tweetMap = defaultdict(list)

    def postTweet(self, userId: int, tweetId: int) -> None:
        self.tweetMap[userId].append((self.time, tweetId))
        self.time += 1

    def getNewsFeed(self, userId: int) -> List[int]:
        feed = self.tweetMap[userId][:]
        for followeeId in self.followMap[userId]:
            feed.extend(self.tweetMap[followeeId])

        feed.sort(key=lambda x: -x[0])
        return [tweetId for _, tweetId in feed[:10]]

    def follow(self, followerId: int, followeeId: int) -> None:
        if followerId != followeeId:
            self.followMap[followerId].add(followeeId)

    def unfollow(self, followerId: int, followeeId: int) -> None:
        self.followMap[followerId].discard(followeeId)

Time & Space Complexity

  • Time complexity: O(nm+tlogt)O(n * m + t\log t) for each getNewsFeed()getNewsFeed() call and O(1)O(1) for remaining methods.
  • Space complexity: O(Nm+NM)O(N * m + N * M)

Where nn is the total number of followeeIdsfolloweeIds associated with the userIduserId, mm is the maximum number of tweets by any user, tt is the total number of tweets associated with the userIduserId and its followeeIdsfolloweeIds, NN is the total number of userIdsuserIds and MM is the maximum number of followees for any user.


2. Heap

Intuition

Each user can follow many people, and each of those people may have many tweets.
Instead of combining all tweets and sorting them (which is slow), we only need the 10 most recent tweets.

We use a min-heap (priority queue) because:

  • We push only the latest tweet of each followee.
  • Each tweet has a timestamp (count), where smaller means more recent.
  • We repeatedly extract the most recent tweet and then push the next tweet from that same user.
  • This is similar to merging K sorted lists efficiently.

This ensures:

  • We never sort huge lists.
  • The heap always contains at most “number of followees” entries.
  • We only perform work proportional to the 10 tweets we need.

Algorithm

  1. Maintain:

    • tweetMap[user] -> list of (time, tweetId), sorted by recency
    • followMap[user] -> set of followees
    • count -> a decreasing timestamp for ordering tweets
  2. postTweet(user, tweetId)

    • Store (count, tweetId) in the user's list
    • Decrease count (more negative = more recent)
  3. getNewsFeed(user)

    • Make sure user follows themselves
    • For each followee:
      • Push their most recent tweet into a min-heap:
        [time, tweetId, followeeId, nextIndex]
    • While heap is not empty and result has < 10 tweets:
      • Pop the most recent tweet
      • Add tweetId to the result
      • If the followee has older tweets, push the next one into the heap
    • Return the collected tweets
  4. follow(follower, followee)

    • Add followee to the follower's follow-set
  5. unfollow(follower, followee)

    • Remove followee if present
class Twitter:
    def __init__(self):
        self.count = 0
        self.tweetMap = defaultdict(list)  # userId -> list of [count, tweetIds]
        self.followMap = defaultdict(set)  # userId -> set of followeeId

    def postTweet(self, userId: int, tweetId: int) -> None:
        self.tweetMap[userId].append([self.count, tweetId])
        self.count -= 1

    def getNewsFeed(self, userId: int) -> List[int]:
        res = []
        minHeap = []

        self.followMap[userId].add(userId)
        for followeeId in self.followMap[userId]:
            if followeeId in self.tweetMap:
                index = len(self.tweetMap[followeeId]) - 1
                count, tweetId = self.tweetMap[followeeId][index]
                heapq.heappush(minHeap, [count, tweetId, followeeId, index - 1])

        while minHeap and len(res) < 10:
            count, tweetId, followeeId, index = heapq.heappop(minHeap)
            res.append(tweetId)
            if index >= 0:
                count, tweetId = self.tweetMap[followeeId][index]
                heapq.heappush(minHeap, [count, tweetId, followeeId, index - 1])
        return res

    def follow(self, followerId: int, followeeId: int) -> None:
        self.followMap[followerId].add(followeeId)

    def unfollow(self, followerId: int, followeeId: int) -> None:
        if followeeId in self.followMap[followerId]:
            self.followMap[followerId].remove(followeeId)

Time & Space Complexity

  • Time complexity: O(nlogn)O(n \log n) for each getNewsFeed()getNewsFeed() call and O(1)O(1) for remaining methods.
  • Space complexity: O(Nm+NM+n)O(N * m + N * M + n)

Where nn is the total number of followeeIdsfolloweeIds associated with the userIduserId, mm is the maximum number of tweets by any user, NN is the total number of userIdsuserIds and MM is the maximum number of followees for any user.


3. Heap (Optimal)

Intuition

The basic heap solution looks at all tweets of all followees, which is fast enough but can be improved.

Key observation:

  • Each user only cares about the latest 10 tweets, because the news feed returns at most 10 items.
  • So for each user, instead of storing all tweets, store only their 10 most recent tweets.
  • This reduces:
    • Memory usage
    • Heap operations
    • Time per query

The trick:

  • When a user posts a tweet, append it with a decreasing timestamp (count) and keep only the last 10 tweets.
  • When getting the news feed:
    • If the user follows many people (>= 10), we first gather only the recent tweets that could appear in the final 10, using a max-heap limited to size 10.
    • Otherwise, push the most recent tweet of each followee directly into a min-heap and expand like a K-sorted-list merge.
  • In both cases, we never process more than 10 tweets per followee, and never extract more than 10 results.

This makes the method very fast even when users post a lot of tweets.

Algorithm

  1. postTweet(user, tweetId)

    • Insert (timestamp, tweetId) at the end of that user's list.
    • If the list grows beyond 10, remove the oldest tweet.
    • Decrease global timestamp so newer tweets have smaller values.
  2. getNewsFeed(user)
    Steps:

    • Ensure user follows themselves.
    • If followees >= 10:
      • Build a max-heap that stores only the top 10 most recent tweets across followees.
      • Convert it to a min-heap for final processing.
    • Else:
      • Push the newest tweet from each followee into a min-heap.
    • Repeatedly pop from the heap:
      • Add the tweet to the result
      • Push the next tweet from the same followee (if exists)
      • Stop after 10 tweets
    • Return the collected tweets.
  3. follow(follower, followee)

    • Add followee to follower's follow-set
  4. unfollow(follower, followee)

    • Remove the followee if present
class Twitter:

    def __init__(self):
        self.count = 0
        self.tweetMap = defaultdict(list)  # userId -> list of [count, tweetIds]
        self.followMap = defaultdict(set)  # userId -> set of followeeId

    def postTweet(self, userId: int, tweetId: int) -> None:
        self.tweetMap[userId].append([self.count, tweetId])
        if len(self.tweetMap[userId]) > 10:
            self.tweetMap[userId].pop(0)
        self.count -= 1

    def getNewsFeed(self, userId: int) -> List[int]:
        res = []
        minHeap = []
        self.followMap[userId].add(userId)
        if len(self.followMap[userId]) >= 10:
            maxHeap = []
            for followeeId in self.followMap[userId]:
                if followeeId in self.tweetMap:
                    index = len(self.tweetMap[followeeId]) - 1
                    count, tweetId = self.tweetMap[followeeId][index]
                    heapq.heappush(maxHeap, [-count, tweetId, followeeId, index - 1])
                    if len(maxHeap) > 10:
                        heapq.heappop(maxHeap)
            while maxHeap:
                count, tweetId, followeeId, index = heapq.heappop(maxHeap)
                heapq.heappush(minHeap, [-count, tweetId, followeeId, index])
        else:
            for followeeId in self.followMap[userId]:
                if followeeId in self.tweetMap:
                    index = len(self.tweetMap[followeeId]) - 1
                    count, tweetId = self.tweetMap[followeeId][index]
                    heapq.heappush(minHeap, [count, tweetId, followeeId, index - 1])

        while minHeap and len(res) < 10:
            count, tweetId, followeeId, index = heapq.heappop(minHeap)
            res.append(tweetId)
            if index >= 0:
                count, tweetId = self.tweetMap[followeeId][index]
                heapq.heappush(minHeap, [count, tweetId, followeeId, index - 1])

        return res

    def follow(self, followerId: int, followeeId: int) -> None:
        self.followMap[followerId].add(followeeId)

    def unfollow(self, followerId: int, followeeId: int) -> None:
        if followeeId in self.followMap[followerId]:
            self.followMap[followerId].remove(followeeId)

Time & Space Complexity

  • Time complexity: O(n)O(n) for each getNewsFeed()getNewsFeed() call and O(1)O(1) for remaining methods.
  • Space complexity: O(Nm+NM+n)O(N * m + N * M + n)

Where nn is the total number of followeeIdsfolloweeIds associated with the userIduserId, mm is the maximum number of tweets by any user (mm can be at most 1010), NN is the total number of userIdsuserIds and MM is the maximum number of followees for any user.


Common Pitfalls

Forgetting to Include User's Own Tweets

When building the news feed, a user should see their own tweets in addition to tweets from people they follow. A common mistake is only iterating through followees without including the user themselves.

# Wrong - missing user's own tweets
for followeeId in self.followMap[userId]:
    # Only gets followees' tweets

# Correct - include user in the feed
self.followMap[userId].add(userId)  # Add self before iterating
for followeeId in self.followMap[userId]:
    # Now includes user's own tweets

Using Wrong Heap Type or Direction

The heap-based solution requires careful attention to whether you need a min-heap or max-heap. Since we want the most recent tweets (highest timestamp), using a min-heap requires negating timestamps, while a max-heap extracts them directly.

# With min-heap, negate count to get max behavior
heapq.heappush(minHeap, [count, tweetId, ...])  # count is negative

# Common mistake: forgetting to negate when using min-heap
heapq.heappush(minHeap, [self.time, tweetId, ...])  # Wrong if time is positive

Allowing Self-Follow in Follow Method

While the news feed logic may internally add the user to their own follow set, the follow() method should prevent explicit self-following to avoid duplicate entries or unexpected behavior.

# Wrong - allows self-follow
def follow(self, followerId: int, followeeId: int) -> None:
    self.followMap[followerId].add(followeeId)

# Correct - prevent self-follow
def follow(self, followerId: int, followeeId: int) -> None:
    if followerId != followeeId:
        self.followMap[followerId].add(followeeId)