Mid to Senior Engineer

System Design Interview Prep

A structured path from the interview framework through core concepts, key technologies and patterns to eighteen full problem breakdowns, each with diagrams and weak, solid and excellent answers to every deep dive.

Chapter 21 of 36Problem breakdowns · Design a News Feed and Notification System

Design a News Feed and Notification System

A social feed is the standard example of a read-heavy system with a hard asymmetry: one write must become visible to many readers. The central question is when to do that work, at write time or at read time, and the right answer is rarely either extreme. Notifications are the natural companion, because they are the same fan-out problem delivered to a device.

This chapter builds the design step by step, then goes deep on the four places where interviewers push hardest. Each deep dive compares a weak, a solid and an excellent answer.

1. Understanding the problem

Users publish short posts and follow other users. When a user opens the app, they see a home feed: recent posts from the people they follow.

Functional requirements

Core:

  1. A user can create a post (text, optionally media).
  2. A user can follow and unfollow other users.
  3. A user can view a feed of recent posts from people they follow, newest first, with the ability to keep scrolling.

Confirm in or out of scope: likes and comments, ranking beyond reverse-chronological order, notifications, search, and private accounts. A sensible opening: "I will build posting, following and the home feed, then come back to ranking and notifications."

Non-functional requirements

  • Fast feed loads, under a few hundred milliseconds. Opening the feed is the most frequent action.
  • Fast propagation of new posts, within a few seconds. This is eventual consistency, and it is acceptable: nobody notices a post arriving a second late.
  • High availability. A broken feed is a broken product, so favour availability over strict consistency.
  • Scale to hundreds of millions of users, with some accounts having tens of millions of followers.

Estimation

Assume 300 million daily active users. Each posts 0.5 times a day and opens the feed 10 times a day. The average user follows and is followed by about 200 accounts.

QuantityCalculationResult
Posts per dayabout 1,700 per second
Feed reads per dayabout 35,000 per second
Push fan-out writesabout 350,000 timeline inserts per second
Timeline cache800 post ids 8 bytes usersabout 1.9 TB

What the numbers say. Reads outnumber posts by about twenty to one, so the feed read path must be cheap. Naive push multiplies each post by the follower count, and the average hides the real hazard: one account with 50 million followers turns a single post into 50 million writes. The 1.9 TB timeline cache fits a sharded in-memory store.

2. The set up

Core entities

  • User and the follow relationship (a directed edge from follower to followee).
  • Post: identifier, author, time, text, media references.
  • Timeline: for a user, an ordered list of recent post identifiers.

API

POST /v1/posts            { "text": "...", "media_ids": [...] }
POST /v1/follow/{user_id}
GET  /v1/feed?cursor=...  -> { "posts": [...], "next_cursor": "..." }

Use cursor-based pagination, not page numbers. New posts arrive while the user scrolls, so offsets shift and produce duplicates and gaps. A cursor such as "posts older than this identifier" stays stable.

Post identifiers should be time-sortable, so sorting identifiers sorts by time and a single index serves both ordering and lookup.

Data model

  • A follow graph, stored so that you can list the followers of a user and the accounts a user follows. That is two access paths, so it is usually stored twice.
  • Posts, keyed by identifier and also queryable by author and time.
  • A timeline cache keyed by user.
  • Media in object storage, served by a CDN. Posts carry references only.

3. High-level design

Posting and feed reads

The simplest design has a post service, a feed service, a posts store and the follow graph. To read a feed, the feed service looks up everyone the user follows, fetches their recent posts and merges them. That works for a toy, and it is exactly the fan-out on read model that the numbers rule out at scale: a user who follows 500 accounts causes 500 lookups on every feed open, at 35,000 opens a second.

So the production design precomputes. Each user has a timeline, a list of recent post identifiers kept in a cache. Reading a feed becomes one cache read plus a batch fetch of post bodies. Writing a post triggers work to update the timelines.

That work must not block the person posting. The post service writes the post, emits an event to a queue and returns. Fan-out workers consume the event, read the author's followers from the graph and insert the post identifier into each follower's timeline.

<!--fig:hld-->
1 write 2 event followers 3 insert post id read timeline Client APIgateway Post service Feed service Posts store post-created Fan-outworkers Follow graph Timeline cache post ids per user Feed service then hydrates post bodies from the posts store in one batch. Figure 1. Posting is synchronous only up to the database write; fan-out runs asynchronously behind a queue.

This asynchronous split is the key architectural decision. A spike in posting grows the queue and delays feed freshness by seconds, rather than failing posts.

Following

Following writes an edge to the graph and, as a nicety, backfills a few of the new followee's recent posts into the follower's timeline. Unfollowing writes a deletion and relies on read-time filtering, described in the deep dives.

4. Potential deep dives

Deep dive 1: Fan-out on write or on read?

The challenge. Where does the cost of connecting posts to readers land, and what happens to the account with 50 million followers?

Weak: fan-out on read only. Store posts by author, and on each feed open fetch and merge the recent posts of every followee. Posting is cheap, and reading is far too expensive. A user following 500 accounts triggers hundreds of lookups plus a merge on every open, which cannot meet a sub-second target at 35,000 reads per second.

Solid: fan-out on write only. At post time, write the post identifier into every follower's timeline. Reads become a single cache lookup, which is exactly what you want for the common action. The weakness is the long tail of heavily followed accounts: one post from an account with 50 million followers triggers 50 million inserts. That takes a long time, delays delivery for everyone, and wastes work on followers who never open the app that day.

Excellent: a hybrid keyed on follower count. Push for ordinary authors, pull for very popular ones.

  • For an author below a follower threshold, fan out on write as above.
  • For an author above the threshold, do not push. Store the post once.
  • At read time, the feed service takes the user's precomputed timeline and merges in the recent posts of the few high-follower accounts they follow, fetched on demand and cached.
<!--fig:hybrid-->
PUSH PATH (authors with a modest follower count) PULL PATH (celebrities, stored once) insert id intoeach timeline GET /feed 1 precomputed list 2 recent celebrity posts Author 200 followers Fan-out workers Followers' timelines post ids Celebrity 50M followers Posts store one copy Feed service merge at read time Reader opensfeed Figure 2. Hybrid fan-out: push for ordinary authors, pull and merge at read time for celebrities.

This bounds the write cost of any single post, since no post triggers more than the threshold number of inserts, and keeps the read cost at one cache read plus a handful of lookups for the popular accounts. Present the threshold as a tunable that you would set from the measured distribution of follower counts.

Two refinements raise the answer further:

  • Skip inactive users. Do not maintain timelines for people who have not opened the app in weeks. Rebuild their timeline by pull when they return.
  • Rate-limit and batch fan-out writes, so that a burst of large fan-outs cannot starve the workers handling ordinary posts. Use separate queues or priorities for the two classes.

Deep dive 2: How do you keep feed reads fast?

The challenge. 35,000 feed reads per second, each of which must return fully populated posts, not just identifiers.

Weak: fetch each post one at a time. A feed of 20 posts means 20 round trips to the posts store, plus author and count lookups. Latency adds up and the store is hammered.

Solid: batch the fetch and cache post objects. Read the timeline identifiers, then fetch all 20 posts in a single multi-get. Cache post objects, since popular posts are requested over and over. Return author details from a user cache.

Excellent: layered caching and denormalised reads. Keep post objects in a distributed cache with a short TTL, and keep counters (likes, replies) in a separate structure updated asynchronously, since they change constantly and tolerate slight staleness. Handle a hot post that goes viral by replicating the hot key across cache nodes or adding a small local cache on each feed server, so one cache node does not become the bottleneck. Page results with a cursor, and prefetch the next page while the user reads the current one.

Deep dive 3: Deletes, edits, follows and privacy

The challenge. A post is deleted, an account is blocked, or someone unfollows. Their post identifiers already sit in thousands of timelines.

Weak: walk every timeline and remove the entry. That means millions of writes for one delete, which is slow and can race with new fan-out.

Solid: filter at read time. Timelines hold identifiers only. When the feed service hydrates posts, it drops any whose post is deleted, whose author is now blocked or unfollowed, or that the viewer is not permitted to see. A background process trims stale identifiers lazily.

Excellent: filter at read time, authorise on every read, and make the source of truth the posts store. Visibility is always decided by current data, never by what happened to be in the timeline when it was written. That also makes privacy changes take effect immediately, because a private account's posts are checked against the viewer's permissions on every read. Edits update the post object once, and every timeline reflects it automatically since timelines hold references.

Deep dive 4: Ranking

The challenge. Reverse-chronological order is simple, but many feeds are ranked.

Weak: sort by time only. It is predictable and easy, and it favours whoever posts most.

Solid: a scoring function over candidates. Take the recent candidates, score each by recency, the viewer's past engagement with the author, and the post's engagement so far, and sort by score.

Excellent: a staged pipeline.

  1. Candidate generation collects a few hundred posts from the timeline and the pulled popular accounts.
  2. Ranking scores candidates with a model whose features are computed partly offline and partly online.
  3. Re-ranking applies rules for diversity, blocked topics and policy.

Describe the stages and where each feature comes from, and do not claim a particular model. Mention that ranking changes the cache design: the timeline holds candidates, and the final order is computed at read time, which costs latency, so the ranking service needs its own latency budget and a fallback to chronological order if it is slow.

5. The notification system

Notifications turn events into messages on a device: push, email, SMS or an in-app inbox. It is a fan-out problem with an extra constraint: you must not send too many, too late or twice.

Architecture

  1. Event sources emit "someone liked your post" or "new follower".
  2. A notification service decides whether to notify, applies the user's preferences (channels, quiet hours, muted accounts) and deduplicates.
  3. One queue per channel decouples the decision from delivery, so a slow email provider cannot delay push messages.
  4. Channel workers call external providers with retries and backoff.
  5. A status store records sent, delivered and failed, which feeds the in-app inbox and debugging.
<!--fig:notify-->
Events like, follow, reply Notificationservice prefs, dedupe, batch push queue email queue sms queue Push workers Email workers SMS workers Push gateway Email provider SMS provider Prefs + status Figure 3. Notification pipeline: one decision point, one queue and worker pool per channel, external providers behind retries.

Points that show depth

  • Idempotency. A retry must not send the same notification twice. Give each notification a deterministic identifier derived from the event, and skip identifiers already sent.
  • Batching and collapsing. Fifty likes in a minute should become one message, "Asha and 49 others liked your post". Hold events briefly in a window, then summarise.
  • Priority lanes. One-time passwords and security alerts use a fast, separate path from marketing and social notifications.
  • Provider failure. Keep a fallback provider and a dead-letter queue, and alert on the depth of the dead-letter queue.
  • Token hygiene. Device tokens go stale. Remove the ones that providers report as invalid, so you stop sending to them.
  • Compliance. Honour opt-outs and consent requirements for each channel, and keep a record of consent. Rules differ by country, so say you would confirm them rather than quote them.

6. What is expected at each level

Mid-level. You reach the idea of precomputed timelines and an asynchronous fan-out through a queue, with prompting. You use cursor pagination and explain why. You acknowledge the celebrity problem when it is pointed out and propose a reasonable fix.

Senior. You raise the push-versus-pull trade-off unprompted, justify the hybrid with numbers, and handle deletes and privacy by filtering at read time. You discuss failure: what happens when the fan-out queue backs up, and how freshness degrades gracefully.

Staff. You reframe the problem around product behaviour: how fresh does a feed need to be, what does ranking cost in latency and money, and how are abuse and spam controlled. You design the thresholds and fallbacks as operable knobs, discuss multi-region replication of timelines, and describe how you would measure success.

7. Interview questions and model answers

Q: Push or pull for the feed? Hybrid. Push gives fast reads for most users, but it is ruinous for accounts with millions of followers, so those are pulled and merged at read time. I would make the threshold configurable and skip pushing to inactive users.

Q: A celebrity with 50 million followers posts. What happens? Nothing is pushed. The post is stored once, and when a follower opens the feed, the server merges recent posts from the popular accounts they follow into their timeline. Those posts are hot, so they are cached and replicated across cache nodes.

Q: How do you handle a deleted post? Mark it deleted and filter it at read time. I clean timelines lazily in the background, so deleting never requires touching millions of lists at once.

Q: Why cursor pagination? New posts arrive while the user scrolls, so offsets shift and cause duplicates or gaps. A cursor anchored on a post identifier is stable.

Q: What if the fan-out queue falls behind? Feed freshness degrades by seconds or minutes, while posting still succeeds. I would alert on queue lag, scale the workers, and prioritise fan-out for users who are currently active.

Q: How do you avoid spamming people with notifications? Per-user preferences, deduplication, rate limits, collapsing bursts into a single summary, and a separate priority path for security messages.

8. Common mistakes

  • Choosing pure push and ignoring the celebrity case.
  • Choosing pure pull and ignoring the read cost.
  • Fetching post bodies one at a time.
  • Page-number pagination on a feed.
  • Doing fan-out synchronously inside the post request.
  • Deleting by touching every timeline instead of filtering at read time.
  • No plan for sending the same notification twice.
Header Logo