System design · Feeds and ranking
Design a review-based feed with caching, push and pull
- Reported in several places
- senior SDE
- 2 diagrams
- 2 sources
Two or more independent public pages, at least one a candidate write-up.
Design a feed where shoppers scroll through product reviews and style posts from people and brands they follow, mixed with popular reviews from categories they browse. Assume 20 million daily users who open the feed 10 times a day, and some brand accounts with 5 million followers.
A representative prompt in the style reported, not a quote from Myntra. The answer below is written for this site.
Ask first
Clarify the problem before you draw anything.
First questions: what is an item in the feed: a review with photos, a style post, or both? Whose items appear: followed people and brands only, or also popular reviews for products and categories I look at? Is ranking by time, or by helpfulness and relevance? Do reviews pass moderation before they show? How fresh must the feed be, and how large can a follower list get?
Assumed scope: items come from followed accounts, blended with popular category reviews; newest first with a light popularity boost; reviews appear only after moderation. In scope: publish, fan-out, feed read, caching, pagination, deletes. Out of scope: the ranking model itself, the review authoring UI, abuse detection, and search.
Numbers
A back-of-envelope that decides the shape.
Planning figures for the exercise, not facts about any company.
- Reads: 20,000,000 users × 10 opens = 200,000,000 a day ÷ 86,400 = about 2,300 a second. A sale event might hit 10× that: 23,000 feed reads a second, past the 20,000 line, so caching is required.
- Item lookups: a page shows 20 items but hydrates about 40 candidates, so 40 × 23,000 = 920,000 item reads a second (460,000 if only the 20 shown were read). These must come from a cache of review bodies, not the primary.
- Writes: 500,000 new reviews a day is about 6 a second. Creating the review is trivial.
- Fan-out on write: with an average of 200 followers, 500,000 × 200 = 100,000,000 feed inserts a day, about 1,160 a second; at a 10× peak, 11,600 a second, past the 5,000 write line for a relational primary. Feed lists therefore live in a partitioned store built for it, such as an in-memory or wide-column store.
- The big brand: one post to 5,000,000 followers at 20,000 inserts a second takes 5,000,000 ÷ 20,000 = 250 s, and ties up the fan-out workers while it runs.
- Feed storage: 200 entries × 16 B = 3.2 KB a user; for 30,000,000 active users that is about 96 GB of raw ids and scores, and more once the store adds its own per-entry overhead, so several nodes, not one.
The numbers say: cache hard, push for ordinary authors, and pull for the few huge accounts.
The design
Diagrams first, then components, data and the hard parts.
- A review is saved as pending. When moderation approves it, the status change and an outbox row commit in one transaction.
- The outbox publishes review.approved to the queue.
- If the author has fewer than about 500,000 followers, workers add the review id to each active follower's feed list, 1,000 followers at a time.
- Above the threshold nothing is pushed. The post goes to the author's own recent-posts list.
- A shopper opens the feed.
- The API reads the first page of ids from the user's feed list (ids only, never content).
- It also pulls the recent-posts list of each big account the user follows, and blends in popular category reviews.
- It hydrates the ids from the item cache, drops deleted, hidden or blocked items, and returns 20.
Open on the whiteboardAll 2 diagrams on one tidy board, with a step-by-step walkthrough.
Components. A review service and database; a moderation step; an outbox and queue; fan-out workers; a follower graph store; a feed store holding per-user lists of item ids; an item cache; and a feed API that assembles pages.
Data model.
reviews(review_id, product_id, author_id, rating, text, media, status, created_at)with a time-sortable id, so ordering by id is ordering by time.follows(author_id, follower_id)indexed both ways: the followers of an author for fan-out, and the authors of a follower for pull.- Feed list per user: a sorted set of review ids scored by time, capped at 200 entries. It holds ids only, never content.
Write path.
- The review is saved as pending. Moderation approves it, and the status change and an outbox row commit in one transaction.
- The outbox publishes
review.approvedto a queue. - If the author has fewer than about 500,000 followers (a tunable inside the 100,000 to 1,000,000 range), workers page through followers 1,000 at a time and add the id to each active follower's feed list, with a batched write. Adding the same id twice is a no-op because the structure is a set, so retries are safe. Inactive users (not seen for 30 days) are skipped.
- If the author is above the threshold, nothing is pushed. The item is written to the author's own recent-posts list and cached.
Read path.
- Fetch the first page of ids from the user's feed list.
- Pull: for each big account the user follows (typically a few dozen), read its cached recent-posts list.
- Blend in popular reviews for the user's top categories from a shared, global list refreshed every few minutes.
- Merge, de-duplicate and sort by score. Fetch about twice the page size as candidates.
- Hydrate the ids with a multi-get against the item cache, dropping anything deleted, hidden, or from an account the user blocked. Return 20.
Pagination. Use a cursor made of the last item's (score, id), not an offset, so new items arriving do not shift pages and cause repeats.
Caching layers. The popular-by-category list is the same for many users and has the best hit rate (cache about 5 minutes). Big-account lists are cached 10 to 30 s; they are the hottest keys, read by a large share of all opens, so also keep them in a small in-process cache on each API instance for a few seconds. The assembled first page can be cached per user for 30 s, with pull-to-refresh bypassing it. The item cache holds review bodies for about a minute; counters like helpful votes are kept separately so they can lag.
Cold and returning users. A user with no feed list gets one built on first read by pulling from followed accounts, then it is maintained. A new follow back-fills the author's last ten reviews; an unfollow is applied lazily by filtering at read time.
Deletes and edits. A deleted or rejected review is a tombstone: hydration drops it even if the id is still in a thousand feed lists, the tombstone is also written to the item cache so a cached body cannot outlive it, and a cleanup job removes ids later.
Trade-offs
What you chose, and when you would flip.
- Push vs pull vs hybrid. Push makes reads cheap but wastes work for inactive followers and breaks on huge accounts. Pull is always fresh but multiplies read cost by accounts followed. Pick the hybrid with a threshold near 500,000 followers; lower it if fan-out workers regularly fall behind, raise it if pull reads get slow.
- Ids in the feed vs full content. Ids keep feed lists small (3.2 KB a user) and make deletes and edits instant. Full copies avoid a hydration hop but multiply storage and leave stale copies. Pick ids; copy content only for a tiny fixed window if hydration latency is a problem.
- Per-user page cache vs computing each time. Caching the first page cuts load on the merge step, but a user may miss something posted a second ago. Pick a 30 s cache with a refresh bypass. Flip to no page cache when freshness matters more than load.
- Time order vs ranked. Time order is predictable and cheap. Ranking raises engagement but needs features and a model call per candidate. Start with time plus a popularity boost; flip to a ranker over the top 200 candidates when the product has the data and the latency budget.
Follow-ups to expect
Where the interviewer pushes next.
- A user follows 5,000 accounts. What breaks in the pull path, and how do you contain it?
- How would you remove a review from every feed within seconds after a legal takedown?
- A brand drops its follower count from 5 million to 400,000 overnight. What happens to its posts?
- How would you rank by helpfulness without recomputing a score on every read?
- What do you do when the feed store loses a node and a million lists vanish?
Where candidates slip
The mistakes this question punishes.
- Pure fan-out on write with no threshold, so one brand post queues five million writes and delays everyone else.
- Pure pull: a join across all followed accounts on every open, at 23,000 opens a second.
- Storing review content in each feed, so edits and deletes never reach the copies.
- Offset pagination, so a new post shifts the page and shows duplicates.
- Pushing to every follower including users who have not opened the app in months.
- Showing a review before moderation, or never filtering at read time, so removed content stays visible.
- Never saying how stale the feed may be.
Have a go before you read.
Interviews reward the sketch you make cold. Clarify out loud, draw it, then compare with the answer.
Now make it stick
Where this was reported
Public pages only. Companies do not confirm these, and they rotate their questions, so treat each link as one person's account or one list's tag.