The "Design Twitter" problem asks you to implement a simplified version of Twitter. You are to design a system with the following features:
postTweet(userId, tweetId)
: Compose a new tweet with ID tweetId
by user userId
.
getNewsFeed(userId)
: Retrieve the 10 most recent tweet IDs in the user's news feed. The news feed should show tweets from the user themselves and from users they follow, ordered from most recent to least recent.
follow(followerId, followeeId)
: Follower with ID followerId
starts following user with ID followeeId
.
unfollow(followerId, followeeId)
: Follower with ID followerId
stops following user with ID followeeId
.
Key constraints:
tweetId
and is timestamped according to posting order.At first glance, you might think to store all tweets in a single list, then filter and sort them every time a user wants their news feed. However, this would be very inefficient, especially as the number of tweets grows.
Instead, we want to design data structures that allow:
We realize that for each user, we need to keep track of who they follow and also maintain a record of their own tweets. For the news feed, we need a way to efficiently merge the most recent tweets from multiple users.
Let's break down the solution step by step:
postTweet
is called, add the tweet to the user's list with the current timestamp.
This approach ensures all operations are efficient and scalable, even as the number of users or tweets grows.
Let's walk through a sample scenario:
postTweet(1, 5)
getNewsFeed(1)
follow(2, 1)
getNewsFeed(2)
unfollow(2, 1)
getNewsFeed(2)
If multiple users post tweets, the news feed merges the tweets by timestamp and returns the 10 most recent.
Brute-force Approach:
getNewsFeed
, scan all tweets in the system and filter those posted by the user or their followees. This takes O(N) time per query, where N is the total number of tweets.
postTweet
: O(1) time and space per tweet.
follow
/ unfollow
: O(1) time and space per operation.
getNewsFeed
: For each user, we only merge the most recent tweets from themselves and each followee. If there are k users (themselves + followees), and each has up to 10 recent tweets, merging is O(k log k) (using a heap). Since k is typically much smaller than the total user base, this is efficient.
In summary, the "Design Twitter" problem is a classic system design and data structure challenge. The key insights are:
import heapq
from collections import defaultdict, deque
class Twitter:
def __init__(self):
self.timestamp = 0
self.tweets = defaultdict(deque) # userId: deque of (timestamp, tweetId)
self.follows = defaultdict(set) # userId: set of followees
def postTweet(self, userId: int, tweetId: int) -> None:
self.tweets[userId].appendleft((self.timestamp, tweetId))
self.timestamp += 1
def getNewsFeed(self, userId: int) -> list:
heap = []
users = set(self.follows[userId])
users.add(userId)
for uid in users:
for i in range(min(10, len(self.tweets[uid]))):
ts, tid = self.tweets[uid][i]
heapq.heappush(heap, (-ts, tid))
res = []
while heap and len(res) < 10:
res.append(heapq.heappop(heap)[1])
return res
def follow(self, followerId: int, followeeId: int) -> None:
if followerId != followeeId:
self.follows[followerId].add(followeeId)
def unfollow(self, followerId: int, followeeId: int) -> None:
if followerId != followeeId:
self.follows[followerId].discard(followeeId)
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include <queue>
using namespace std;
class Twitter {
int timestamp;
unordered_map<int, unordered_set<int>> follows;
unordered_map<int, vector<pair<int, int>>> tweets;
public:
Twitter() : timestamp(0) {}
void postTweet(int userId, int tweetId) {
tweets[userId].push_back({timestamp++, tweetId});
}
vector<int> getNewsFeed(int userId) {
struct Tweet {
int time, tid;
Tweet(int t, int id) : time(t), tid(id) {}
bool operator<(const Tweet& other) const { return time < other.time; }
};
priority_queue<Tweet> pq;
unordered_set<int> users = follows[userId];
users.insert(userId);
for (int uid : users) {
int n = tweets[uid].size();
for (int i = max(0, n-10); i < n; ++i) {
pq.push(Tweet(tweets[uid][i].first, tweets[uid][i].second));
}
}
vector<int> res;
while (!pq.empty() && res.size() < 10) {
res.push_back(pq.top().tid);
pq.pop();
}
return res;
}
void follow(int followerId, int followeeId) {
if (followerId != followeeId) follows[followerId].insert(followeeId);
}
void unfollow(int followerId, int followeeId) {
if (followerId != followeeId) follows[followerId].erase(followeeId);
}
};
import java.util.*;
class Twitter {
private static int timestamp = 0;
private Map<Integer, Set<Integer>> follows;
private Map<Integer, LinkedList<Tweet>> tweets;
private static class Tweet {
int time, tid;
Tweet(int t, int id) { time = t; tid = id; }
}
public Twitter() {
follows = new HashMap<>();
tweets = new HashMap<>();
}
public void postTweet(int userId, int tweetId) {
tweets.putIfAbsent(userId, new LinkedList<>());
tweets.get(userId).addFirst(new Tweet(timestamp++, tweetId));
}
public List<Integer> getNewsFeed(int userId) {
PriorityQueue<Tweet> pq = new PriorityQueue<>((a, b) -> b.time - a.time);
Set<Integer> users = new HashSet<>(follows.getOrDefault(userId, new HashSet<>()));
users.add(userId);
for (int uid : users) {
LinkedList<Tweet> list = tweets.getOrDefault(uid, new LinkedList<>());
int cnt = 0;
for (Tweet tweet : list) {
pq.offer(tweet);
if (++cnt == 10) break;
}
}
List<Integer> res = new ArrayList<>();
int cnt = 0;
while (!pq.isEmpty() && cnt++ < 10) {
res.add(pq.poll().tid);
}
return res;
}
public void follow(int followerId, int followeeId) {
if (followerId == followeeId) return;
follows.computeIfAbsent(followerId, k -> new HashSet<>()).add(followeeId);
}
public void unfollow(int followerId, int followeeId) {
if (followerId == followeeId) return;
if (follows.containsKey(followerId))
follows.get(followerId).remove(followeeId);
}
}
class Twitter {
constructor() {
this.timestamp = 0;
this.tweets = new Map(); // userId: array of [timestamp, tweetId]
this.follows = new Map(); // userId: set of followees
}
postTweet(userId, tweetId) {
if (!this.tweets.has(userId)) this.tweets.set(userId, []);
this.tweets.get(userId).unshift([this.timestamp++, tweetId]);
}
getNewsFeed(userId) {
let users = new Set(this.follows.get(userId) || []);
users.add(userId);
let heap = [];
for (let uid of users) {
let list = this.tweets.get(uid) || [];
for (let i = 0; i < Math.min(10, list.length); ++i) {
heap.push(list[i]);
}
}
heap.sort((a, b) => b[0] - a[0]);
return heap.slice(0, 10).map(x => x[1]);
}
follow(followerId, followeeId) {
if (followerId === followeeId) return;
if (!this.follows.has(followerId)) this.follows.set(followerId, new Set());
this.follows.get(followerId).add(followeeId);
}
unfollow(followerId, followeeId) {
if (followerId === followeeId) return;
if (this.follows.has(followerId)) this.follows.get(followerId).delete(followeeId);
}
}