Skip to main content

355. Design Twitter

mediumAsked at Twilio

Design Twitter is Twilio's feed-system probe: implement postTweet, follow, unfollow, and getNewsFeed (the 10 most recent tweets from a user and the people they follow). The grading rubric weighs whether you choose a fan-out-on-read model with K-way merge for the feed or fan-out-on-write, and articulate the tradeoff.

By Alex Chen, Founder, InterviewChamp.AI · Last verified

Source citations

Public interview reports confirming this problem appears in Twilio loops.

  • Glassdoor (2026-Q1)Listed in Twilio platform-team on-site reports as a 'feed-system in code' design question.
  • LeetCode Discuss (2025-12)Cited in Twilio interview reports as a hybrid design-and-implementation question.

Problem

Design a simplified version of Twitter where users can post tweets, follow/unfollow another user, and is able to see the 10 most recent tweets in the user's news feed. Implement the Twitter class with postTweet(userId, tweetId), getNewsFeed(userId) (returns the 10 most recent tweet IDs in the user's feed, ordered most recent first; the feed must contain tweets from the user themselves and the people they follow), follow(followerId, followeeId), unfollow(followerId, followeeId).

Constraints

  • 1 <= userId, followerId, followeeId <= 500
  • 0 <= tweetId <= 10^4
  • All the tweets have unique IDs.
  • At most 3 * 10^4 calls will be made to postTweet, getNewsFeed, follow, and unfollow.

Examples

Example 1

Input
postTweet(1,5), getNewsFeed(1), follow(1,2), postTweet(2,6), getNewsFeed(1), unfollow(1,2), getNewsFeed(1)
Output
[null, [5], null, null, [6,5], null, [5]]

Approaches

1. Fan-out on write — per-user materialized feed (rejected for the constraint set)

On post, append the tweet to every follower's pre-materialized feed. getNewsFeed reads the user's feed directly.

Time
post O(F) where F = follower count, feed O(1)
Space
O(F * tweets)
// Sketch only — at this scale fan-out-on-write wastes memory because
// most users will never call getNewsFeed during the test sequence.
class TwitterFanOut {
  constructor() { this.feeds = new Map(); this.follows = new Map(); }
  postTweet(userId, tweetId) {
    const followers = [userId, ...(this.followersOf(userId))];
    for (const u of followers) {
      if (!this.feeds.has(u)) this.feeds.set(u, []);
      this.feeds.get(u).unshift(tweetId);
      if (this.feeds.get(u).length > 10) this.feeds.get(u).length = 10;
    }
  }
  // ... etc. Note: follow doesn't backfill historical posts; un/follow gets messy.
}

Tradeoff: Wins for read-heavy production workloads (Twitter, Instagram), but for the LC contract — small N (500 users), small calls (3e4) — fan-out-on-read is simpler and avoids messy follow/unfollow backfill semantics.

2. Fan-out on read — per-user tweet list + heap merge on feed (optimal for this scale)

Each user has their own list of (timestamp, tweetId) tweets. getNewsFeed gathers tweet lists from self+followees and K-way merges the 10 most recent.

Time
post O(1), feed O(F * log F) for K=10
Space
O(total tweets + total follows)
// Inline min-heap for K-way merge
class MinHeap {
  constructor() { this.h = []; }
  // Each entry is [-timestamp, listRef, idx] so smaller -timestamp = more recent
  push(v) {
    this.h.push(v);
    let i = this.h.length - 1;
    while (i > 0) {
      const p = (i - 1) >> 1;
      if (this.h[p][0] <= this.h[i][0]) break;
      [this.h[p], this.h[i]] = [this.h[i], this.h[p]];
      i = p;
    }
  }
  pop() {
    const top = this.h[0];
    const last = this.h.pop();
    if (this.h.length > 0) {
      this.h[0] = last;
      let i = 0; const n = this.h.length;
      while (true) {
        const l = 2 * i + 1, r = 2 * i + 2;
        let s = i;
        if (l < n && this.h[l][0] < this.h[s][0]) s = l;
        if (r < n && this.h[r][0] < this.h[s][0]) s = r;
        if (s === i) break;
        [this.h[s], this.h[i]] = [this.h[i], this.h[s]];
        i = s;
      }
    }
    return top;
  }
  size() { return this.h.length; }
}

class Twitter {
  constructor() {
    this.tweets = new Map(); // userId -> array of [time, tweetId]
    this.follows = new Map(); // userId -> Set<followeeId>
    this.time = 0;
  }
  postTweet(userId, tweetId) {
    if (!this.tweets.has(userId)) this.tweets.set(userId, []);
    this.tweets.get(userId).push([this.time++, tweetId]);
  }
  getNewsFeed(userId) {
    const candidates = new Set([userId, ...(this.follows.get(userId) || [])]);
    const heap = new MinHeap();
    for (const u of candidates) {
      const list = this.tweets.get(u);
      if (!list || list.length === 0) continue;
      const idx = list.length - 1;
      heap.push([-list[idx][0], list, idx]);
    }
    const result = [];
    while (result.length < 10 && heap.size() > 0) {
      const [negT, list, idx] = heap.pop();
      result.push(list[idx][1]);
      if (idx > 0) heap.push([-list[idx - 1][0], list, idx - 1]);
    }
    return result;
  }
  follow(a, b) {
    if (!this.follows.has(a)) this.follows.set(a, new Set());
    this.follows.get(a).add(b);
  }
  unfollow(a, b) {
    if (this.follows.has(a)) this.follows.get(a).delete(b);
  }
}

Tradeoff: K-way merge over F sources (where F is self + followees) to extract the top 10. Each pop and replace-with-next is O(log F). For F = 500 followees, feed is ~10 * log(500) ~ 90 ops — well within the constraint budget.

Twilio-specific tips

Twilio's bar on Design Twitter is the read-vs-write tradeoff articulation. State both models, explain the workload they each favor, then pick fan-out-on-read for the LC constraint set (low follower counts, low feed-call frequency). Mentioning that production Twitter uses a hybrid (fan-out-on-write for normal users, fan-out-on-read for celebrities with millions of followers) is a strong system-design signal.

Common mistakes

  • Storing a global tweet feed and filtering by following list at read time — O(total tweets) per getNewsFeed, fails the constraints at scale.
  • Forgetting that the user's OWN tweets must appear in their feed — easy to miss because follow() doesn't add yourself.
  • Allowing self-follow (follow(1, 1)) to create a phantom duplicate in the feed — guard or use a Set for candidate users.

Follow-up questions

An interviewer at Twilio may pivot to one of these next:

  • How would you scale this to 100M users? (Hybrid fan-out — write to inboxes for normal users, read-merge for celebrities. Cassandra/Redis for inboxes.)
  • How would you support feed pagination beyond 10? (Same K-way merge with an offset cursor; pop offset items and continue.)
  • How would you support 'unread' state per follower? (Per-user last-seen timestamp; the feed query becomes 'tweets newer than last-seen'.)

Solve it now

Free. No sign-up. Python and JavaScript run instantly in your browser.

Output

Press Run or Cmd+Enter to execute

FAQ

Why use a heap instead of just sorting the merged tweet list?

Because we only need the top 10. K-way merge with a heap is O(K log F) where K=10 and F is the number of source lists. Sorting everything is O(total_tweets log total_tweets) — wasteful.

Why negate the timestamp instead of using a max-heap?

Because JS doesn't ship a heap and writing a min-heap is the canonical reference implementation. Negation converts max-on-time to min-on-(-time) without re-implementing the heap with reversed comparators.

Free learning resources

Curated free links for this problem.

Practice these live with InterviewChamp.AI

Drill Design Twitter and other Twilio interview questions under real-loop conditions with instant feedback on your reasoning, complexity claims, and code.

Practice these live with InterviewChamp.AI →