Bird
Raised Fist0
Interview Prepcustom-data-structuresmediumAmazonGoogle

Design Twitter (Top K Recent Tweets)

Choose your preparation mode4 modes available
🎯
Design Twitter (Top K Recent Tweets)
mediumDESIGNAmazonGoogle

Imagine building a mini Twitter where users can post tweets and see the most recent tweets from people they follow. How do you efficiently retrieve the top K recent tweets?

💡 This is a design problem that requires managing multiple users' tweet streams and efficiently merging them to get the most recent tweets. Beginners often struggle with how to combine multiple sorted lists efficiently and how to design data structures that support fast updates and queries.
📋
Problem Statement

Design a simplified version of Twitter where users can post tweets, follow/unfollow other users, and retrieve the 10 most recent tweets in their news feed. Each tweet has a unique ID and a timestamp. Implement the following methods: - postTweet(userId, tweetId): Compose a new tweet. - getNewsFeed(userId): Retrieve the 10 most recent tweet IDs in the user's news feed. Tweets must be ordered from most recent to least recent. - follow(followerId, followeeId): Follower follows a followee. - unfollow(followerId, followeeId): Follower unfollows a followee. Assume all tweets are timestamped in the order they are posted.

1 ≤ userId, followerId, followeeId ≤ 10^5Tweet IDs are unique integersAt most 10^5 calls will be made to all methodsEach user can follow at most 10^4 usersgetNewsFeed returns up to 10 tweets
💡
Example
Input"postTweet(1, 5)\ngetNewsFeed(1)"
Output[5]

User 1 posts a tweet with ID 5. Their news feed returns this tweet.

Input"postTweet(1, 5)\nfollow(1, 2)\npostTweet(2, 6)\ngetNewsFeed(1)"
Output[6, 5]

User 1 follows user 2. User 2 posts tweet 6. User 1's news feed shows tweets 6 and 5 in order.

  • User follows no one and has no tweets → getNewsFeed returns empty list
  • User follows themselves only → getNewsFeed returns their own tweets
  • User unfollows someone they do not follow → no effect
  • Multiple tweets posted at the same timestamp → order by posting sequence
⚠️
Common Mistakes
Sorting all tweets every time in getNewsFeed

Leads to timeouts or slow responses on large inputs

✅ Use a heap to merge sorted tweet lists instead of sorting all tweets

Not including user's own tweets in their news feed

User's own tweets never appear in their feed, failing correctness

✅ Always add userId to the set of users considered in getNewsFeed

Allowing a user to follow themselves multiple times or unfollow themselves

Can cause inconsistent data or errors

✅ Prevent self-follow or ignore unfollow if followerId == followeeId

Using inefficient data structures like arrays for frequent insertions at head

PostTweet becomes O(n) instead of O(1), slowing down the system

✅ Use linked lists or data structures supporting O(1) head insertion

Not handling empty followee lists or missing users gracefully

Code throws exceptions or returns wrong results

✅ Use safe lookups and default empty sets/lists

🧠
Brute Force (Scan All Tweets Each Time)
💡 This approach exists to establish a baseline understanding by directly scanning all tweets from followed users every time we need the news feed. It is simple but inefficient, highlighting why better data structures are needed.

Intuition

For each getNewsFeed call, gather all tweets from the user and their followees, then sort them by timestamp to pick the top 10 most recent tweets.

Algorithm

  1. Maintain a map from userId to list of their tweets (each tweet has a timestamp).
  2. Maintain a map from userId to set of followees.
  3. To getNewsFeed(userId), collect all tweets from userId and their followees.
  4. Sort all collected tweets by timestamp descending and return the top 10 tweet IDs.
💡 The main challenge is realizing that scanning and sorting all tweets every time is expensive and will not scale.
</>
Code
import heapq

class Twitter:
    def __init__(self):
        self.time = 0
        self.tweets = {}  # userId -> list of (time, tweetId)
        self.followees = {}  # userId -> set of followeeIds

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

    def getNewsFeed(self, userId: int) -> list:
        users = self.followees.get(userId, set()).copy()
        users.add(userId)
        all_tweets = []
        for u in users:
            if u in self.tweets:
                all_tweets.extend(self.tweets[u])
        all_tweets.sort(key=lambda x: x[0], reverse=True)
        return [tweetId for _, tweetId in all_tweets[:10]]

    def follow(self, followerId: int, followeeId: int) -> None:
        if followerId not in self.followees:
            self.followees[followerId] = set()
        self.followees[followerId].add(followeeId)

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

# Driver code
if __name__ == '__main__':
    twitter = Twitter()
    twitter.postTweet(1, 5)
    print(twitter.getNewsFeed(1))  # [5]
    twitter.follow(1, 2)
    twitter.postTweet(2, 6)
    print(twitter.getNewsFeed(1))  # [6, 5]
Line Notes
self.time = 0Initialize global timestamp to order tweets chronologically
self.tweets = {}Store tweets per user for direct access
self.followees = {}Track who each user follows
self.time += 1Increment timestamp on each new tweet to maintain order
self.tweets[userId].append((self.time, tweetId))Add tweet with timestamp to user's tweet list
users = self.followees.get(userId, set()).copy()Get all followees of user safely
users.add(userId)Include user's own tweets in news feed
all_tweets.extend(self.tweets[u])Collect all tweets from each followed user
all_tweets.sort(key=lambda x: x[0], reverse=True)Sort tweets by timestamp descending to get recent first
return [tweetId for _, tweetId in all_tweets[:10]]Return top 10 tweet IDs
self.followees[followerId].add(followeeId)Add followee to follower's followee set
self.followees[followerId].discard(followeeId)Remove followee if exists, no error if not
import java.util.*;

class Twitter {
    private static int time = 0;
    private Map<Integer, List<int[]>> tweets; // userId -> list of [time, tweetId]
    private Map<Integer, Set<Integer>> followees; // userId -> set of followees

    public Twitter() {
        tweets = new HashMap<>();
        followees = new HashMap<>();
    }

    public void postTweet(int userId, int tweetId) {
        time++;
        tweets.putIfAbsent(userId, new ArrayList<>());
        tweets.get(userId).add(new int[]{time, tweetId});
    }

    public List<Integer> getNewsFeed(int userId) {
        Set<Integer> users = new HashSet<>();
        if (followees.containsKey(userId)) {
            users.addAll(followees.get(userId));
        }
        users.add(userId);
        List<int[]> allTweets = new ArrayList<>();
        for (int u : users) {
            if (tweets.containsKey(u)) {
                allTweets.addAll(tweets.get(u));
            }
        }
        allTweets.sort((a, b) -> b[0] - a[0]);
        List<Integer> res = new ArrayList<>();
        for (int i = 0; i < Math.min(10, allTweets.size()); i++) {
            res.add(allTweets.get(i)[1]);
        }
        return res;
    }

    public void follow(int followerId, int followeeId) {
        followees.putIfAbsent(followerId, new HashSet<>());
        followees.get(followerId).add(followeeId);
    }

    public void unfollow(int followerId, int followeeId) {
        if (followees.containsKey(followerId)) {
            followees.get(followerId).remove(followeeId);
        }
    }

    // Driver code
    public static void main(String[] args) {
        Twitter twitter = new Twitter();
        twitter.postTweet(1, 5);
        System.out.println(twitter.getNewsFeed(1)); // [5]
        twitter.follow(1, 2);
        twitter.postTweet(2, 6);
        System.out.println(twitter.getNewsFeed(1)); // [6, 5]
    }
}
Line Notes
private static int time = 0;Global timestamp to order tweets
tweets = new HashMap<>();Map user to their tweets
followees = new HashMap<>();Map user to their followees
time++;Increment timestamp on each tweet
tweets.putIfAbsent(userId, new ArrayList<>());Initialize tweet list if missing
allTweets.sort((a, b) -> b[0] - a[0]);Sort tweets descending by timestamp
users.add(userId);Include user's own tweets
followees.putIfAbsent(followerId, new HashSet<>());Initialize followee set if missing
followees.get(followerId).add(followeeId);Add followee
followees.get(followerId).remove(followeeId);Remove followee if exists
#include <iostream>
#include <vector>
#include <unordered_map>
#include <unordered_set>
#include <algorithm>

using namespace std;

class Twitter {
    int time;
    unordered_map<int, vector<pair<int,int>>> tweets; // userId -> vector of (time, tweetId)
    unordered_map<int, unordered_set<int>> followees; // userId -> set of followees
public:
    Twitter() {
        time = 0;
    }

    void postTweet(int userId, int tweetId) {
        time++;
        tweets[userId].push_back({time, tweetId});
    }

    vector<int> getNewsFeed(int userId) {
        unordered_set<int> users;
        if (followees.count(userId)) {
            users = followees[userId];
        }
        users.insert(userId);
        vector<pair<int,int>> allTweets;
        for (int u : users) {
            if (tweets.count(u)) {
                allTweets.insert(allTweets.end(), tweets[u].begin(), tweets[u].end());
            }
        }
        sort(allTweets.begin(), allTweets.end(), [](auto &a, auto &b) {
            return a.first > b.first;
        });
        vector<int> res;
        for (int i = 0; i < (int)min(10, (int)allTweets.size()); i++) {
            res.push_back(allTweets[i].second);
        }
        return res;
    }

    void follow(int followerId, int followeeId) {
        followees[followerId].insert(followeeId);
    }

    void unfollow(int followerId, int followeeId) {
        if (followees.count(followerId)) {
            followees[followerId].erase(followeeId);
        }
    }
};

// Driver code
int main() {
    Twitter twitter;
    twitter.postTweet(1, 5);
    vector<int> feed1 = twitter.getNewsFeed(1);
    for (int id : feed1) cout << id << " ";
    cout << endl; // 5
    twitter.follow(1, 2);
    twitter.postTweet(2, 6);
    vector<int> feed2 = twitter.getNewsFeed(1);
    for (int id : feed2) cout << id << " ";
    cout << endl; // 6 5
    return 0;
}
Line Notes
int time;Global timestamp to order tweets
unordered_map<int, vector<pair<int,int>>> tweets;Store tweets per user
unordered_map<int, unordered_set<int>> followees;Track followees per user
time++;Increment timestamp on each tweet
tweets[userId].push_back({time, tweetId});Add tweet with timestamp
users.insert(userId);Include user's own tweets
allTweets.insert(allTweets.end(), tweets[u].begin(), tweets[u].end());Collect tweets from each followed user
sort(allTweets.begin(), allTweets.end(), ...Sort tweets descending by timestamp
followees[followerId].insert(followeeId);Add followee
followees[followerId].erase(followeeId);Remove followee if exists
class Twitter {
    constructor() {
        this.time = 0;
        this.tweets = new Map(); // userId -> array of [time, tweetId]
        this.followees = new Map(); // userId -> set of followees
    }

    postTweet(userId, tweetId) {
        this.time++;
        if (!this.tweets.has(userId)) {
            this.tweets.set(userId, []);
        }
        this.tweets.get(userId).push([this.time, tweetId]);
    }

    getNewsFeed(userId) {
        const users = new Set();
        if (this.followees.has(userId)) {
            for (const f of this.followees.get(userId)) {
                users.add(f);
            }
        }
        users.add(userId);
        let allTweets = [];
        for (const u of users) {
            if (this.tweets.has(u)) {
                allTweets = allTweets.concat(this.tweets.get(u));
            }
        }
        allTweets.sort((a, b) => b[0] - a[0]);
        return allTweets.slice(0, 10).map(x => x[1]);
    }

    follow(followerId, followeeId) {
        if (!this.followees.has(followerId)) {
            this.followees.set(followerId, new Set());
        }
        this.followees.get(followerId).add(followeeId);
    }

    unfollow(followerId, followeeId) {
        if (this.followees.has(followerId)) {
            this.followees.get(followerId).delete(followeeId);
        }
    }
}

// Driver code
const twitter = new Twitter();
twitter.postTweet(1, 5);
console.log(twitter.getNewsFeed(1)); // [5]
twitter.follow(1, 2);
twitter.postTweet(2, 6);
console.log(twitter.getNewsFeed(1)); // [6, 5]
Line Notes
this.time = 0;Global timestamp to order tweets
this.tweets = new Map();Map user to their tweets
this.followees = new Map();Map user to their followees
this.time++;Increment timestamp on each tweet
this.tweets.get(userId).push([this.time, tweetId]);Add tweet with timestamp
const users = new Set();Collect all users to consider for news feed
users.add(userId);Include user's own tweets
allTweets.sort((a, b) => b[0] - a[0]);Sort tweets descending by timestamp
this.followees.get(followerId).add(followeeId);Add followee
this.followees.get(followerId).delete(followeeId);Remove followee if exists
⏱
Complexity
TimeO(F * T log (F * T)) where F is number of followees + 1, T is average tweets per user
SpaceO(U + T) where U is number of users and T total tweets stored

Each getNewsFeed collects all tweets from followed users and sorts them, which is expensive when many tweets exist.

💡 If a user follows 1000 users each with 100 tweets, sorting 100,000 tweets each time is very slow.
Interview Verdict: TLE / Inefficient for large inputs

This approach is too slow for large data but helps understand the problem and motivates better solutions.

🧠
Better Approach (Use Max Heap for K-Way Merge)
💡 This approach improves efficiency by merging the tweet lists of followed users using a max heap, avoiding sorting all tweets at once.

Intuition

Each user’s tweets are stored in descending order by timestamp. To get the news feed, we perform a k-way merge of these sorted lists using a max heap to efficiently find the next most recent tweet.

Algorithm

  1. Store each user’s tweets in a linked list or array in descending order of timestamp.
  2. Maintain a map of followees for each user.
  3. For getNewsFeed(userId), initialize a max heap and push the most recent tweet from each followee and the user.
  4. Pop from the heap to get the next most recent tweet, then push the next tweet from the same user’s list if available.
  5. Repeat until 10 tweets are collected or heap is empty.
💡 The key insight is to merge sorted lists efficiently using a heap, which is faster than sorting all tweets together.
</>
Code
import heapq

class Twitter:
    def __init__(self):
        self.time = 0
        self.tweets = {}  # userId -> list of (time, tweetId)
        self.followees = {}  # userId -> set of followeeIds

    def postTweet(self, userId: int, tweetId: int) -> None:
        self.time += 1
        if userId not in self.tweets:
            self.tweets[userId] = []
        self.tweets[userId].insert(0, (self.time, tweetId))  # newest at front

    def getNewsFeed(self, userId: int) -> list:
        users = self.followees.get(userId, set()).copy()
        users.add(userId)
        heap = []  # max heap simulated by pushing negative time
        indices = {}  # userId -> current index in tweets list

        for u in users:
            if u in self.tweets and self.tweets[u]:
                indices[u] = 0
                time, tweetId = self.tweets[u][0]
                heapq.heappush(heap, (-time, tweetId, u))

        res = []
        while heap and len(res) < 10:
            neg_time, tweetId, u = heapq.heappop(heap)
            res.append(tweetId)
            indices[u] += 1
            if indices[u] < len(self.tweets[u]):
                time, nextTweetId = self.tweets[u][indices[u]]
                heapq.heappush(heap, (-time, nextTweetId, u))
        return res

    def follow(self, followerId: int, followeeId: int) -> None:
        if followerId not in self.followees:
            self.followees[followerId] = set()
        self.followees[followerId].add(followeeId)

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

# Driver code
if __name__ == '__main__':
    twitter = Twitter()
    twitter.postTweet(1, 5)
    print(twitter.getNewsFeed(1))  # [5]
    twitter.follow(1, 2)
    twitter.postTweet(2, 6)
    print(twitter.getNewsFeed(1))  # [6, 5]
Line Notes
self.tweets[userId].insert(0, (self.time, tweetId))Insert newest tweet at front for descending order
users = self.followees.get(userId, set()).copy()Get all followees safely
users.add(userId)Include user's own tweets
heap = []Initialize max heap (using negative time)
indices = {}Track current index in each user's tweet list
heapq.heappush(heap, (-time, tweetId, u))Push most recent tweet from each user
neg_time, tweetId, u = heapq.heappop(heap)Pop tweet with max timestamp
indices[u] += 1Move to next tweet in user's list
if indices[u] < len(self.tweets[u])If more tweets exist, push next one to heap
import java.util.*;

class Twitter {
    private static int time = 0;
    private Map<Integer, LinkedList<int[]>> tweets; // userId -> list of [time, tweetId]
    private Map<Integer, Set<Integer>> followees; // userId -> set of followees

    public Twitter() {
        tweets = new HashMap<>();
        followees = new HashMap<>();
    }

    public void postTweet(int userId, int tweetId) {
        time++;
        tweets.putIfAbsent(userId, new LinkedList<>());
        tweets.get(userId).addFirst(new int[]{time, tweetId});
    }

    public List<Integer> getNewsFeed(int userId) {
        Set<Integer> users = new HashSet<>();
        if (followees.containsKey(userId)) {
            users.addAll(followees.get(userId));
        }
        users.add(userId);

        PriorityQueue<int[]> heap = new PriorityQueue<>((a, b) -> b[0] - a[0]);
        Map<Integer, Integer> indices = new HashMap<>();

        for (int u : users) {
            if (tweets.containsKey(u) && !tweets.get(u).isEmpty()) {
                indices.put(u, 0);
                int[] tweet = tweets.get(u).get(0);
                heap.offer(new int[]{tweet[0], tweet[1], u});
            }
        }

        List<Integer> res = new ArrayList<>();
        while (!heap.isEmpty() && res.size() < 10) {
            int[] top = heap.poll();
            res.add(top[1]);
            int u = top[2];
            int idx = indices.get(u) + 1;
            if (idx < tweets.get(u).size()) {
                indices.put(u, idx);
                int[] nextTweet = tweets.get(u).get(idx);
                heap.offer(new int[]{nextTweet[0], nextTweet[1], u});
            }
        }
        return res;
    }

    public void follow(int followerId, int followeeId) {
        followees.putIfAbsent(followerId, new HashSet<>());
        followees.get(followerId).add(followeeId);
    }

    public void unfollow(int followerId, int followeeId) {
        if (followees.containsKey(followerId)) {
            followees.get(followerId).remove(followeeId);
        }
    }

    // Driver code
    public static void main(String[] args) {
        Twitter twitter = new Twitter();
        twitter.postTweet(1, 5);
        System.out.println(twitter.getNewsFeed(1)); // [5]
        twitter.follow(1, 2);
        twitter.postTweet(2, 6);
        System.out.println(twitter.getNewsFeed(1)); // [6, 5]
    }
}
Line Notes
tweets.get(userId).addFirst(new int[]{time, tweetId});Add newest tweet at front for descending order
Set<Integer> users = new HashSet<>();Collect all users to consider
users.add(userId);Include user's own tweets
PriorityQueue<int[]> heap = new PriorityQueue<>((a, b) -> b[0] - a[0]);Max heap by timestamp
indices.put(u, 0);Track current index in user's tweet list
heap.offer(new int[]{tweet[0], tweet[1], u});Push most recent tweet from user
int[] top = heap.poll();Pop tweet with max timestamp
indices.put(u, idx);Update index for next tweet
heap.offer(new int[]{nextTweet[0], nextTweet[1], u});Push next tweet from same user
#include <iostream>
#include <vector>
#include <unordered_map>
#include <unordered_set>
#include <queue>

using namespace std;

class Twitter {
    int time;
    unordered_map<int, vector<pair<int,int>>> tweets; // userId -> vector of (time, tweetId)
    unordered_map<int, unordered_set<int>> followees; // userId -> set of followees
public:
    Twitter() {
        time = 0;
    }

    void postTweet(int userId, int tweetId) {
        time++;
        tweets[userId].insert(tweets[userId].begin(), {time, tweetId});
    }

    vector<int> getNewsFeed(int userId) {
        unordered_set<int> users;
        if (followees.count(userId)) {
            users = followees[userId];
        }
        users.insert(userId);

        struct cmp {
            bool operator()(const tuple<int,int,int>& a, const tuple<int,int,int>& b) {
                return get<0>(a) < get<0>(b); // max heap by time
            }
        };

        priority_queue<tuple<int,int,int>, vector<tuple<int,int,int>>, cmp> heap;
        unordered_map<int,int> indices;

        for (int u : users) {
            if (tweets.count(u) && !tweets[u].empty()) {
                indices[u] = 0;
                auto& t = tweets[u][0];
                heap.emplace(t.first, t.second, u);
            }
        }

        vector<int> res;
        while (!heap.empty() && (int)res.size() < 10) {
            auto [timeStamp, tweetId, u] = heap.top(); heap.pop();
            res.push_back(tweetId);
            indices[u]++;
            if (indices[u] < (int)tweets[u].size()) {
                auto& t = tweets[u][indices[u]];
                heap.emplace(t.first, t.second, u);
            }
        }
        return res;
    }

    void follow(int followerId, int followeeId) {
        followees[followerId].insert(followeeId);
    }

    void unfollow(int followerId, int followeeId) {
        if (followees.count(followerId)) {
            followees[followerId].erase(followeeId);
        }
    }
};

// Driver code
int main() {
    Twitter twitter;
    twitter.postTweet(1, 5);
    vector<int> feed1 = twitter.getNewsFeed(1);
    for (int id : feed1) cout << id << " ";
    cout << endl; // 5
    twitter.follow(1, 2);
    twitter.postTweet(2, 6);
    vector<int> feed2 = twitter.getNewsFeed(1);
    for (int id : feed2) cout << id << " ";
    cout << endl; // 6 5
    return 0;
}
Line Notes
tweets[userId].insert(tweets[userId].begin(), {time, tweetId});Insert newest tweet at front for descending order
unordered_set<int> users;Collect all users to consider
users.insert(userId);Include user's own tweets
priority_queue<tuple<int,int,int>, ...> heap;Max heap by timestamp
indices[u] = 0;Track current index in user's tweet list
heap.emplace(t.first, t.second, u);Push next tweet from same user
auto [timeStamp, tweetId, u] = heap.top();Pop tweet with max timestamp
indices[u]++;Move to next tweet
class Twitter {
    constructor() {
        this.time = 0;
        this.tweets = new Map(); // userId -> array of [time, tweetId]
        this.followees = new Map(); // userId -> set of followees
    }

    postTweet(userId, tweetId) {
        this.time++;
        if (!this.tweets.has(userId)) {
            this.tweets.set(userId, []);
        }
        this.tweets.get(userId).unshift([this.time, tweetId]); // newest at front
    }

    getNewsFeed(userId) {
        const users = new Set();
        if (this.followees.has(userId)) {
            for (const f of this.followees.get(userId)) {
                users.add(f);
            }
        }
        users.add(userId);

        const heap = [];
        const indices = new Map();

        for (const u of users) {
            if (this.tweets.has(u) && this.tweets.get(u).length > 0) {
                indices.set(u, 0);
                const [time, tweetId] = this.tweets.get(u)[0];
                heap.push([-time, tweetId, u]);
            }
        }

        // Heapify
        heap.sort((a, b) => a[0] - b[0]);

        const res = [];
        while (heap.length > 0 && res.length < 10) {
            const [negTime, tweetId, u] = heap.shift();
            res.push(tweetId);
            let idx = indices.get(u) + 1;
            if (idx < this.tweets.get(u).length) {
                indices.set(u, idx);
                const [time, nextTweetId] = this.tweets.get(u)[idx];
                // Insert in sorted order to maintain min-heap
                let inserted = false;
                for (let i = 0; i < heap.length; i++) {
                    if (-time < heap[i][0]) {
                        heap.splice(i, 0, [-time, nextTweetId, u]);
                        inserted = true;
                        break;
                    }
                }
                if (!inserted) {
                    heap.push([-time, nextTweetId, u]);
                }
            }
        }
        return res;
    }

    follow(followerId, followeeId) {
        if (!this.followees.has(followerId)) {
            this.followees.set(followerId, new Set());
        }
        this.followees.get(followerId).add(followeeId);
    }

    unfollow(followerId, followeeId) {
        if (this.followees.has(followerId)) {
            this.followees.get(followerId).delete(followeeId);
        }
    }
}

// Driver code
const twitter = new Twitter();
twitter.postTweet(1, 5);
console.log(twitter.getNewsFeed(1)); // [5]
twitter.follow(1, 2);
twitter.postTweet(2, 6);
console.log(twitter.getNewsFeed(1)); // [6, 5]
Line Notes
this.tweets.get(userId).unshift([this.time, tweetId]);Add newest tweet at front for descending order
const users = new Set();Collect all users to consider
users.add(userId);Include user's own tweets
heap.push([-time, tweetId, u]);Push most recent tweet with negative time for max heap
heap.sort((a, b) => a[0] - b[0]);Heapify by sorting (inefficient but simple)
const [negTime, tweetId, u] = heap.shift();Pop tweet with max timestamp
indices.set(u, idx);Update index for next tweet
heap.splice(i, 0, [-time, nextTweetId, u]);Insert next tweet maintaining heap order
this.followees.get(followerId).add(followeeId);Add followee
this.followees.get(followerId).delete(followeeId);Remove followee if exists
⏱
Complexity
TimeO(N log F) where N is number of tweets retrieved (up to 10) and F is number of followees + 1
SpaceO(F) for heap and indices

Heap merges k sorted lists efficiently, only processing up to 10 tweets per getNewsFeed call.

💡 This approach is much faster than brute force because it avoids sorting all tweets, only merging the top tweets from each user.
Interview Verdict: Accepted / Efficient for large inputs

This is the standard approach to implement in interviews for this problem.

🧠
Optimal Approach (Linked List + HashMap + Heap for O(1) Post and O(k log F) Feed)
💡 This approach uses linked lists to store tweets per user for O(1) insertion and a heap to merge feeds efficiently, combining data structure design with algorithmic optimization.

Intuition

Each user's tweets are stored as a linked list with newest tweet at the head. For getNewsFeed, we use a max heap to merge the heads of each followee's tweet list, popping the most recent and pushing the next tweet from that list.

Algorithm

  1. Define a TweetNode class with tweetId, timestamp, and next pointer.
  2. Store each user's tweets as a linked list with newest tweet at head.
  3. Maintain a map of followees per user.
  4. For getNewsFeed(userId), push the head of each followee's tweet list into a max heap.
  5. Pop from heap to get most recent tweet, then push the next tweet from that user's list.
  6. Repeat until 10 tweets are collected or heap is empty.
💡 This approach optimizes both posting and feed retrieval by using linked lists for O(1) insertion and heaps for efficient merging.
</>
Code
import heapq

class TweetNode:
    def __init__(self, tweetId, time):
        self.tweetId = tweetId
        self.time = time
        self.next = None

class Twitter:
    def __init__(self):
        self.time = 0
        self.userTweets = {}  # userId -> head of linked list of tweets
        self.followees = {}  # userId -> set of followees

    def postTweet(self, userId: int, tweetId: int) -> None:
        self.time += 1
        node = TweetNode(tweetId, self.time)
        node.next = self.userTweets.get(userId, None)
        self.userTweets[userId] = node

    def getNewsFeed(self, userId: int) -> list:
        users = self.followees.get(userId, set()).copy()
        users.add(userId)
        heap = []  # max heap by time

        for u in users:
            head = self.userTweets.get(u, None)
            if head:
                heapq.heappush(heap, (-head.time, head))

        res = []
        while heap and len(res) < 10:
            neg_time, node = heapq.heappop(heap)
            res.append(node.tweetId)
            if node.next:
                heapq.heappush(heap, (-node.next.time, node.next))
        return res

    def follow(self, followerId: int, followeeId: int) -> None:
        if followerId not in self.followees:
            self.followees[followerId] = set()
        self.followees[followerId].add(followeeId)

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

# Driver code
if __name__ == '__main__':
    twitter = Twitter()
    twitter.postTweet(1, 5)
    print(twitter.getNewsFeed(1))  # [5]
    twitter.follow(1, 2)
    twitter.postTweet(2, 6)
    print(twitter.getNewsFeed(1))  # [6, 5]
Line Notes
class TweetNode:Define linked list node for tweets
node.next = self.userTweets.get(userId, None)Insert new tweet at head for O(1) insertion
users = self.followees.get(userId, set()).copy()Get all followees safely
heapq.heappush(heap, (-head.time, head))Push head tweet of each user into max heap
neg_time, node = heapq.heappop(heap)Pop most recent tweet
if node.next: heapq.heappush(heap, (-node.next.time, node.next))Push next tweet from same user
import java.util.*;

class Twitter {
    private static int time = 0;

    private static class TweetNode {
        int tweetId, time;
        TweetNode next;
        TweetNode(int tweetId, int time) {
            this.tweetId = tweetId;
            this.time = time;
            this.next = null;
        }
    }

    private Map<Integer, TweetNode> userTweets;
    private Map<Integer, Set<Integer>> followees;

    public Twitter() {
        userTweets = new HashMap<>();
        followees = new HashMap<>();
    }

    public void postTweet(int userId, int tweetId) {
        time++;
        TweetNode node = new TweetNode(tweetId, time);
        node.next = userTweets.getOrDefault(userId, null);
        userTweets.put(userId, node);
    }

    public List<Integer> getNewsFeed(int userId) {
        Set<Integer> users = new HashSet<>();
        if (followees.containsKey(userId)) {
            users.addAll(followees.get(userId));
        }
        users.add(userId);

        PriorityQueue<TweetNode> heap = new PriorityQueue<>((a, b) -> b.time - a.time);
        for (int u : users) {
            TweetNode head = userTweets.get(u);
            if (head != null) {
                heap.offer(head);
            }
        }

        List<Integer> res = new ArrayList<>();
        while (!heap.isEmpty() && res.size() < 10) {
            TweetNode node = heap.poll();
            res.add(node.tweetId);
            if (node.next != null) {
                heap.offer(node.next);
            }
        }
        return res;
    }

    public void follow(int followerId, int followeeId) {
        followees.putIfAbsent(followerId, new HashSet<>());
        followees.get(followerId).add(followeeId);
    }

    public void unfollow(int followerId, int followeeId) {
        if (followees.containsKey(followerId)) {
            followees.get(followerId).remove(followeeId);
        }
    }

    // Driver code
    public static void main(String[] args) {
        Twitter twitter = new Twitter();
        twitter.postTweet(1, 5);
        System.out.println(twitter.getNewsFeed(1)); // [5]
        twitter.follow(1, 2);
        twitter.postTweet(2, 6);
        System.out.println(twitter.getNewsFeed(1)); // [6, 5]
    }
}
Line Notes
private static class TweetNode {Linked list node for tweets
node.next = userTweets.getOrDefault(userId, null);Insert new tweet at head for O(1) insertion
Set<Integer> users = new HashSet<>();Collect all users to consider
PriorityQueue<TweetNode> heap = new PriorityQueue<>((a, b) -> b.time - a.time);Max heap by timestamp
heap.offer(head);Push head tweet of each user
TweetNode node = heap.poll();Pop most recent tweet
if (node.next != null) heap.offer(node.next);Push next tweet from same user
#include <iostream>
#include <vector>
#include <unordered_map>
#include <unordered_set>
#include <queue>

using namespace std;

class Twitter {
    int time;
    struct TweetNode {
        int tweetId, time;
        TweetNode* next;
        TweetNode(int tId, int t) : tweetId(tId), time(t), next(nullptr) {}
    };

    unordered_map<int, TweetNode*> userTweets;
    unordered_map<int, unordered_set<int>> followees;

public:
    Twitter() {
        time = 0;
    }

    void postTweet(int userId, int tweetId) {
        time++;
        TweetNode* node = new TweetNode(tweetId, time);
        node->next = userTweets[userId];
        userTweets[userId] = node;
    }

    vector<int> getNewsFeed(int userId) {
        unordered_set<int> users;
        if (followees.count(userId)) {
            users = followees[userId];
        }
        users.insert(userId);

        struct cmp {
            bool operator()(TweetNode* a, TweetNode* b) {
                return a->time < b->time; // max heap
            }
        };

        priority_queue<TweetNode*, vector<TweetNode*>, cmp> heap;
        for (int u : users) {
            if (userTweets.count(u) && userTweets[u] != nullptr) {
                heap.push(userTweets[u]);
            }
        }

        vector<int> res;
        while (!heap.empty() && (int)res.size() < 10) {
            TweetNode* node = heap.top(); heap.pop();
            res.push_back(node->tweetId);
            if (node->next != nullptr) {
                heap.push(node->next);
            }
        }
        return res;
    }

    void follow(int followerId, int followeeId) {
        followees[followerId].insert(followeeId);
    }

    void unfollow(int followerId, int followeeId) {
        if (followees.count(followerId)) {
            followees[followerId].erase(followeeId);
        }
    }
};

// Driver code
int main() {
    Twitter twitter;
    twitter.postTweet(1, 5);
    vector<int> feed1 = twitter.getNewsFeed(1);
    for (int id : feed1) cout << id << " ";
    cout << endl; // 5
    twitter.follow(1, 2);
    twitter.postTweet(2, 6);
    vector<int> feed2 = twitter.getNewsFeed(1);
    for (int id : feed2) cout << id << " ";
    cout << endl; // 6 5
    return 0;
}
Line Notes
struct TweetNode {Linked list node for tweets
node->next = userTweets[userId];Insert new tweet at head for O(1) insertion
unordered_set<int> users;Collect all users to consider
priority_queue<TweetNode*, vector<TweetNode*>, cmp> heap;Max heap by timestamp
heap.push(userTweets[u]);Push head tweet of each user
TweetNode* node = heap.top();Pop most recent tweet
if (node->next != nullptr) heap.push(node->next);Push next tweet from same user
class TweetNode {
    constructor(tweetId, time) {
        this.tweetId = tweetId;
        this.time = time;
        this.next = null;
    }
}

class Twitter {
    constructor() {
        this.time = 0;
        this.userTweets = new Map(); // userId -> head TweetNode
        this.followees = new Map(); // userId -> set of followees
    }

    postTweet(userId, tweetId) {
        this.time++;
        const node = new TweetNode(tweetId, this.time);
        node.next = this.userTweets.get(userId) || null;
        this.userTweets.set(userId, node);
    }

    getNewsFeed(userId) {
        const users = new Set();
        if (this.followees.has(userId)) {
            for (const f of this.followees.get(userId)) {
                users.add(f);
            }
        }
        users.add(userId);

        const heap = [];

        for (const u of users) {
            const head = this.userTweets.get(u);
            if (head) {
                heap.push(head);
            }
        }

        // Sort heap by time descending
        heap.sort((a, b) => b.time - a.time);

        const res = [];
        while (heap.length > 0 && res.length < 10) {
            const node = heap.shift();
            res.push(node.tweetId);
            if (node.next) {
                // Insert node.next maintaining descending order
                let inserted = false;
                for (let i = 0; i < heap.length; i++) {
                    if (node.next.time > heap[i].time) {
                        heap.splice(i, 0, node.next);
                        inserted = true;
                        break;
                    }
                }
                if (!inserted) {
                    heap.push(node.next);
                }
            }
        }
        return res;
    }

    follow(followerId, followeeId) {
        if (!this.followees.has(followerId)) {
            this.followees.set(followerId, new Set());
        }
        this.followees.get(followerId).add(followeeId);
    }

    unfollow(followerId, followeeId) {
        if (this.followees.has(followerId)) {
            this.followees.get(followerId).delete(followeeId);
        }
    }
}

// Driver code
const twitter = new Twitter();
twitter.postTweet(1, 5);
console.log(twitter.getNewsFeed(1)); // [5]
twitter.follow(1, 2);
twitter.postTweet(2, 6);
console.log(twitter.getNewsFeed(1)); // [6, 5]
Line Notes
class TweetNode {Define linked list node for tweets
node.next = this.userTweets.get(userId) || null;Insert new tweet at head for O(1) insertion
const users = new Set();Collect all users to consider
heap.push(head);Push head tweet of each user
heap.sort((a, b) => b.time - a.time);Sort heap by timestamp descending
const node = heap.shift();Pop most recent tweet
if (node.next) { ... }Insert next tweet maintaining order
this.followees.get(followerId).add(followeeId);Add followee
this.followees.get(followerId).delete(followeeId);Remove followee if exists
⏱
Complexity
TimeO(k log F) where k=10 tweets retrieved, F is number of followees + 1
SpaceO(F) for heap and O(U) for linked lists

Posting is O(1) due to linked list insertion; feed retrieval merges heads with a heap efficiently.

💡 This is the most efficient design combining fast writes and fast reads, suitable for large scale.
Interview Verdict: Accepted / Optimal

This approach is the best practice for this problem in interviews and real systems.

📊
All Approaches - One-Glance Tradeoffs
💡 In interviews, code the optimal linked list + heap approach unless asked otherwise. Mention brute force to show understanding.
ApproachTimeSpaceStack RiskReconstructUse In Interview
1. Brute ForceO(F*T log (F*T))O(U + T)NoN/AMention only - never code
2. Heap-based K-way MergeO(k log F)O(F)NoN/AGood to code if linked list not required
3. Linked List + Heap (Optimal)O(k log F)O(F + U)NoN/ABest approach to code in interviews
💼
Interview Strategy
💡 Use this guide to understand the problem deeply, practice coding each approach, and prepare to explain tradeoffs clearly in interviews.

How to Present

Step 1: Clarify problem requirements and constraints.Step 2: Describe the brute force approach and its inefficiencies.Step 3: Introduce the heap-based k-way merge approach for better efficiency.Step 4: Present the optimal linked list + heap design for best performance.Step 5: Code the optimal solution and test with examples.

Time Allocation

Clarify: 3min → Approach: 5min → Code: 15min → Test: 7min. Total ~30min

What the Interviewer Tests

Interviewer tests your ability to design efficient data structures, use heaps for merging sorted streams, and handle edge cases like self-follow and empty feeds.

Common Follow-ups

  • What if we want to support unlimited tweets in the feed? → Use pagination or lazy loading.
  • How to optimize memory usage? → Limit stored tweets per user or use database with caching.
💡 Follow-ups test your understanding of scalability and practical system design considerations.
🔍
Pattern Recognition

When to Use

1) Need to merge multiple sorted streams efficiently, 2) Need to maintain recent items per user, 3) Need fast insertion and retrieval, 4) Problem involves social network feed design.

Signature Phrases

top K recent tweetsfollow/unfollow usersnews feedmost recent tweets

NOT This Pattern When

Problems that only require sorting or simple hash maps without merging multiple sorted streams.

Similar Problems

Design Facebook News Feed - similar feed mergingMerge K Sorted Lists - core k-way merge techniqueTop K Frequent Elements - use of heaps for top K queries

Practice

(1/5)
1. Consider the following Python code snippet implementing the optimized Insert Delete GetRandom O(1) with duplicates data structure. After executing the sequence of operations below, what is the return value of the final getRandom() call? Operations: insert(1), insert(1), insert(2), remove(1), getRandom()
import random

class Node:
    def __init__(self, val):
        self.val = val
        self.prev = null
        self.next = null

class DoublyLinkedList:
    def __init__(self):
        self.head = Node(null)
        self.tail = Node(null)
        self.head.next = self.tail
        self.tail.prev = self.head

    def append(self, val):
        node = Node(val)
        node.prev = self.tail.prev
        node.next = self.tail
        self.tail.prev.next = node
        self.tail.prev = node
        return node

    def remove(self, node):
        node.prev.next = node.next
        node.next.prev = node.prev

    def is_empty(self):
        return self.head.next == self.tail

class RandomizedCollection:
    def __init__(self):
        self.nums = []
        self.idx_map = {}

    def insert(self, val: int) -> bool:
        self.nums.append(val)
        if val not in self.idx_map:
            self.idx_map[val] = set()
        self.idx_map[val].add(len(self.nums) - 1)
        return len(self.idx_map[val]) == 1

    def remove(self, val: int) -> bool:
        if val not in self.idx_map or not self.idx_map[val]:
            return false
        remove_idx = self.idx_map[val].pop()
        last_val = self.nums[-1]
        self.nums[remove_idx] = last_val
        self.idx_map[last_val].add(remove_idx)
        self.idx_map[last_val].discard(len(self.nums) - 1)
        self.nums.pop()
        if not self.idx_map[val]:
            del self.idx_map[val]
        return true

    def getRandom(self) -> int:
        return random.choice(self.nums)

rc = RandomizedCollection()
rc.insert(1)
rc.insert(1)
rc.insert(2)
rc.remove(1)
result = rc.getRandom()
easy
A. Either 1 or 2 but 1 is twice as likely
B. 2
C. Either 1 or 2 with equal probability
D. 1

Solution

  1. Step 1: Trace insertions

    After insert(1), nums=[1], idx_map={1:{0}}; after insert(1), nums=[1,1], idx_map={1:{0,1}}; after insert(2), nums=[1,1,2], idx_map={1:{0,1},2:{2}}.
  2. Step 2: Trace removal of one 1

    Remove one 1: remove_idx=1 (pop from idx_map[1]). Swap last element 2 into index 1: nums=[1,2], idx_map={1:{0},2:{1}}. So now nums has one 1 and one 2.
  3. Step 3: Analyze getRandom probabilities

    nums=[1,2], so getRandom picks index 0 or 1 equally. But initially there were two 1's, after removal only one remains. So 1 and 2 are equally likely, but since the original had duplicates, the final collection has one 1 and one 2.
  4. Final Answer:

    Option A -> Option A
  5. Quick Check:

    After removal, one 1 and one 2 remain, so 1 is as likely as 2 [OK]
Hint: Track indices and swaps carefully to find final array state [OK]
Common Mistakes:
  • Forgetting to update idx_map after swap
  • Assuming getRandom returns only 2
  • Ignoring that one 1 remains after removal
2. Given the following Python code for merging two skylines, and the inputs left = [[1, 3], [5, 0]] and right = [[2, 4], [6, 0]], what is the merged skyline returned by mergeSkylines(left, right)?
easy
A. [[1, 3], [2, 4], [5, 0], [6, 0]]
B. [[1, 3], [5, 0], [6, 0]]
C. [[1, 3], [2, 4], [4, 0], [6, 0]]
D. [[1, 3], [2, 4], [4, 0], [5, 0], [6, 0]]

Solution

  1. Step 1: Trace first iterations of the merge loop

    Compare left[0]=(1,3) and right[0]=(2,4): 1 < 2, so x=1, h1=3, h2=0, max_h=3 -> merged=[[1,3]]
  2. Step 2: Next iterations and final merge

    Next left[1]=(5,0), right[0]=(2,4): 2 < 5, x=2, h2=4, h1=3, max_h=4 -> merged=[[1,3],[2,4]]; then right[1]=(6,0) vs left[1]=(5,0): 5 < 6, x=5, h1=0, h2=4, max_h=4 (no change), then x=6, h2=0, max_h=0 -> merged=[[1,3],[2,4],[4,0],[5,0],[6,0]] after extending remaining points.
  3. Final Answer:

    Option D -> Option D
  4. Quick Check:

    Stepwise max height updates match merged skyline [OK]
Hint: Track h1 and h2 updates carefully during merge [OK]
Common Mistakes:
  • Off-by-one in indices
  • Missing key points when heights change
3. Examine the following erase method snippet from a skiplist implementation. Which line contains a subtle bug that can cause nodes to remain in higher levels after deletion, leading to incorrect search results?
medium
A. Line initializing update array with null
B. Line where curr is moved forward after search loop
C. Line updating update[i].forward[i] to skip curr
D. Line missing adjustment of self.level after deletion

Solution

  1. Step 1: Identify erase steps

    Nodes are removed by updating forward pointers at each level where the node exists.
  2. Step 2: Check for level adjustment

    After deletion, if top levels become empty, self.level must be decreased to avoid stale levels.
  3. Final Answer:

    Option D -> Option D
  4. Quick Check:

    Missing level adjustment causes nodes to remain logically in higher levels [OK]
Hint: Always adjust max level after erase to remove empty top levels [OK]
Common Mistakes:
  • Forgetting to update all levels
  • Not decreasing max level after erase
  • Incorrect pointer updates
4. What is the amortized time complexity of the put operation in the optimal LFU Cache implementation using a frequency map of doubly linked lists and hash maps?
medium
A. O(n), where n is the number of keys in the cache
B. O(f), where f is the number of distinct frequencies
C. O(log n), due to maintaining frequency order
D. O(1), amortized constant time

Solution

  1. Step 1: Analyze data structures used

    Hash maps provide O(1) access to keys and frequencies. Doubly linked lists allow O(1) insertion and deletion of keys within frequency groups.
  2. Step 2: Consider operations in put

    Eviction and frequency updates involve O(1) operations on hash maps and linked lists. Frequency increments and min frequency updates are also O(1).
  3. Final Answer:

    Option D -> Option D
  4. Quick Check:

    Optimal LFU cache achieves O(1) amortized put [OK]
Hint: Hash maps + linked lists enable O(1) put [OK]
Common Mistakes:
  • Assuming O(n) due to scanning keys
  • Thinking frequency updates cost O(log n)
  • Confusing distinct frequencies with n
5. Suppose the skyline problem is extended so that buildings can overlap and have negative heights (e.g., representing underground structures). Which modification to the divide and conquer approach is necessary to correctly handle this variant?
hard
A. Modify the merge step to consider absolute values of heights when computing max height
B. Adjust the merge logic to track both max and min heights at each x-coordinate and merge accordingly
C. No modification needed; the existing merge logic works for negative heights
D. Replace divide and conquer with a brute force height map simulation to handle negative heights

Solution

  1. Step 1: Understand impact of negative heights

    Negative heights mean skyline can go below ground, so max height alone is insufficient to represent shape.
  2. Step 2: Modify merge logic accordingly

    Need to track both max and min heights at each x to correctly merge skylines with negative and positive heights.
  3. Step 3: Evaluate other options

    Using absolute values loses sign info; brute force is inefficient; no modification fails correctness.
  4. Final Answer:

    Option B -> Option B
  5. Quick Check:

    Tracking both max and min heights handles negative overlaps correctly [OK]
Hint: Negative heights require tracking min and max heights, not just max [OK]
Common Mistakes:
  • Ignoring negative heights
  • Using absolute values incorrectly
  • Assuming no change needed
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard