Design Twitter Lite
postTweet, follow/unfollow, getNewsFeed — hash maps of tweets and follows, merge k recent feeds for top 10.
advanced3 min read
- dsa
- design
- interview
- Meta
- Amazon
The problem
Design a simplified Twitter:
postTweet(userId, tweetId)getNewsFeed(userId)→ 10 most recent tweet ids from self + followeesfollow(followerId, followeeId)unfollow(followerId, followeeId)
Tweets are ordered by a global recency (post time). Users can follow themselves or not — clarify; typically feed includes own tweets regardless.
Data model
// userId → set of followeeIds
// userId → list of { time, tweetId } newest at end or front
// global clock++
Implementation
class Twitter {
private time = 0;
private tweets = new Map<number, { id: number; t: number }[]>();
private follows = new Map<number, Set<number>>();
private ensureUser(userId: number) {
if (!this.tweets.has(userId)) this.tweets.set(userId, []);
if (!this.follows.has(userId)) {
const s = new Set<number>();
s.add(userId); // always "follow" self for feed simplicity
this.follows.set(userId, s);
}
}
postTweet(userId: number, tweetId: number): void {
this.ensureUser(userId);
this.tweets.get(userId)!.push({ id: tweetId, t: this.time++ });
}
getNewsFeed(userId: number): number[] {
this.ensureUser(userId);
const candidates: { id: number; t: number }[] = [];
for (const uid of this.follows.get(userId)!) {
const list = this.tweets.get(uid) ?? [];
// only need last 10 per user for top-10 global
for (let i = Math.max(0, list.length - 10); i < list.length; i++) {
candidates.push(list[i]);
}
}
candidates.sort((a, b) => b.t - a.t);
return candidates.slice(0, 10).map((x) => x.id);
}
follow(followerId: number, followeeId: number): void {
this.ensureUser(followerId);
this.ensureUser(followeeId);
this.follows.get(followerId)!.add(followeeId);
}
unfollow(followerId: number, followeeId: number): void {
if (followerId === followeeId) return; // keep self
this.follows.get(followerId)?.delete(followeeId);
}
}
| op | time (this version) |
|---|---|
| post | O(1) amortized |
| follow/unfollow | O(1) |
| getNewsFeed | O(F · 10 + F·10 log) sort small |
Heap merge (interview upgrade)
If users have huge tweet lists, merge with a max-heap of pointers into each followee’s newest tweets — classic k-way merge. Mention it; for LC constraints, collect+sort of last 10 each is enough.
// sketch: push {tweet, user, index} for each followee's last tweet
// pop 10 times, each time push previous tweet from same user
Brute design
Store all tweets globally, filter by follow set each feed call. O(total tweets) per feed — fails at scale, fine as “what not to do.”
Edge cases
- Unfollow self — ignore
- Follow same user twice
- Empty feed
- User posts before ensureUser on followee
- Exactly 10+ tweets — order by time, not tweetId
Common mistakes
- Forgetting own tweets in feed
- Sorting by tweetId instead of time
- Unfollow deleting self from set permanently
- Mutating shared follow sets across users
Interview delivery
- APIs + recency clock.
- Maps for tweets and adjacency of follows.
- Feed = merge self+followees, top 10.
- Edge cases on unfollow self.
- Complexity + heap optimization talk track.
Related
- LRU Cache
- Time Based Key Value Store
- Top K Frequent Elements
- Find Median from Data Stream
- Meta Frontend Interview