Design a simplified Twitter where users post tweets and follow each other. getNewsFeedmust return the 10 most-recent tweets from a user's followees (including themselves) in O(k log k) time using a k-way merge via a max-heap over per-user tweet cursors.
Implement four methods on a Twitter class: postTweet(userId, tweetId), getNewsFeed(userId) → the 10 newest tweet IDs from people the user follows (including themselves), follow, and unfollow.
Concrete example.User 1 posts tweets 5, 3 (in that order). User 2 posts tweet 10. User 1 follows user 2. getNewsFeed(1) → [10, 3, 5]— tweet 10 is newest (latest global timestamp), then user 1's own tweets newest-first. After unfollow(1, 2), getNewsFeed(1) → [3, 5].
ts that increments with every postTweet. This gives tweets a comparable total order across users without any real wall clock.[{ tweetId, time }, …]. Because you always push, the array is already sorted ascending — newest tweet is at the tail.getNewsFeed, collect the set of peers (self ∪ followees). For each peer who has tweets, create a cursor pointing at their last (newest) tweet.time(that's the globally newest unconsumed tweet), emit its tweetId, advance that cursor one step left. Remove exhausted cursors. A real heap does this in O(log k) per pick; the linear scan shown here is O(k) per pick but identical in logic.Map<number, Set<number>> gives O(1) add/delete. Nothing else needs to change — the merge reads the set fresh every call.userIdtoo, even if they don't appear in their own following set.Space: O(T) for all tweet logs (unavoidable) + O(F) for the follow-graph where F = total follow edges.
Real heap vs linear scan: with a true priority queue (e.g. MinHeapkeyed by negative timestamp) each pick is O(log k). With a linear scan over k cursors each pick is O(k). Both give the same result; the heap wins when k is large (a celebrity followed by millions).
1▶class Twitter {2▶ private static ts = 0; // global monotonic timestamp3▶ private tweets = new Map<number, { tweetId: number; time: number }[]>();4▶ private following = new Map<number, Set<number>>();56 postTweet(userId: number, tweetId: number): void {7 if (!this.tweets.has(userId)) this.tweets.set(userId, []);8 this.tweets.get(userId)!.push({ tweetId, time: Twitter.ts++ });9 }1011 getNewsFeed(userId: number): number[] {12 // Collect latest tweet from every followee (plus self)13 const peers = new Set([userId, ...(this.following.get(userId) ?? [])]);14 // Min-heap keyed by time (max-10 merge via a max-heap simulation)15 // We use a simple sorted merge of per-user pointers for clarity.16 type Cursor = { list: { tweetId: number; time: number }[]; idx: number };17 const cursors: Cursor[] = [];18 for (const uid of peers) {19 const list = this.tweets.get(uid) ?? [];20 if (list.length > 0) cursors.push({ list, idx: list.length - 1 });21 }2223 const result: number[] = [];24 while (result.length < 10 && cursors.length > 0) {25 // pick the cursor with the largest time (max-heap by time)26 let best = 0;27 for (let i = 1; i < cursors.length; i++) {28 if (cursors[i].list[cursors[i].idx].time >29 cursors[best].list[cursors[best].idx].time) {30 best = i;31 }32 }33 const cur = cursors[best];34 result.push(cur.list[cur.idx].tweetId);35 if (cur.idx === 0) {36 cursors.splice(best, 1); // exhausted this user's list37 } else {38 cur.idx--;39 }40 }41 return result;42 }4344 follow(followerId: number, followeeId: number): void {45 if (!this.following.has(followerId)) this.following.set(followerId, new Set());46 this.following.get(followerId)!.add(followeeId);47 }4849 unfollow(followerId: number, followeeId: number): void {50 this.following.get(followerId)?.delete(followeeId);51 }52}
class Twitter {
private static ts = 0; // global monotonic timestamp
private tweets = new Map<number, { tweetId: number; time: number }[]>();
private following = new Map<number, Set<number>>();
postTweet(userId: number, tweetId: number): void {
if (!this.tweets.has(userId)) this.tweets.set(userId, []);
this.tweets.get(userId)!.push({ tweetId, time: Twitter.ts++ });
}
getNewsFeed(userId: number): number[] {
// Collect latest tweet from every followee (plus self)
const peers = new Set([userId, ...(this.following.get(userId) ?? [])]);
// Min-heap keyed by time (max-10 merge via a max-heap simulation)
// We use a simple sorted merge of per-user pointers for clarity.
type Cursor = { list: { tweetId: number; time: number }[]; idx: number };
const cursors: Cursor[] = [];
for (const uid of peers) {
const list = this.tweets.get(uid) ?? [];
if (list.length > 0) cursors.push({ list, idx: list.length - 1 });
}
const result: number[] = [];
while (result.length < 10 && cursors.length > 0) {
// pick the cursor with the largest time (max-heap by time)
let best = 0;
for (let i = 1; i < cursors.length; i++) {
if (cursors[i].list[cursors[i].idx].time >
cursors[best].list[cursors[best].idx].time) {
best = i;
}
}
const cur = cursors[best];
result.push(cur.list[cur.idx].tweetId);
if (cur.idx === 0) {
cursors.splice(best, 1); // exhausted this user's list
} else {
cur.idx--;
}
}
return result;
}
follow(followerId: number, followeeId: number): void {
if (!this.following.has(followerId)) this.following.set(followerId, new Set());
this.following.get(followerId)!.add(followeeId);
}
unfollow(followerId: number, followeeId: number): void {
this.following.get(followerId)?.delete(followeeId);
}
}tweets maps each user to their append-only tweet log (sorted ascending by time because we always push). following maps each user to the set of people they follow. A Set gives O(1) follow/unfollow and deduplicates.{ list, idx: list.length - 1 }pointing at their newest tweet. This is the "seed" step of k-way merge — one entry per source.time (linear scan here; replace with a heap for O(log k)). Emit its tweetId, advance its index. If the cursor hits index −1, splice it out (that user's tweets are exhausted).Set.add and Set.delete. No need to rebuild any index — getNewsFeed reads the set fresh every time it is called.postTweet: O(1). follow/unfollow: O(1). getNewsFeed: O(k · 10) with a linear scan = O(k), or O(10 log k) with a real max-heap — where k = number of followees. The result is always at most 10 items.time; each of the 10 pops is O(log k). Total: O(k + 10 log k).Map<number, Tweet> keyed by tweetId and look up after the merge.| "merge N sorted streams / lists" | k-way merge with max-heap |
| "most recent across multiple users" | global timestamp + cursors |
| "design a social feed" | per-user tweet log + follow set |
| "top-10 from multiple sorted arrays" | heap with one cursor per array |
class Twitter {
private static ts = 0;
private tweets = new Map<number, { tweetId: number; time: number }[]>();
private following = new Map<number, Set<number>>();
postTweet(userId: number, tweetId: number): void {
// push { tweetId, time: Twitter.ts++ } to tweets[userId]
}
getNewsFeed(userId: number): number[] {
// merge cursors (self + followees), pick max-time 10 times
const result: number[] = [];
// while result.length < 10 && cursors.length > 0 → find best, advance
return result;
}
follow(followerId: number, followeeId: number): void { /* add to Set */ }
unfollow(followerId: number, followeeId: number): void { /* delete from Set */ }
}getNewsFeed(1) returns: