SysDesignPrep.com
Interviewer kit

Design Reddit

Run this for someone else. You hold the answers; they do not. Read the prompt, keep the clock, and use the probes below when an answer is thin. Do not show them this page.

The candidate should have a blank page and a whiteboard, not this.

Open with this

Serve communities of posts with deeply nested comment threads, take tens of thousands of votes a second, rank by hot and best, and keep a 50,000-comment thread fast while it is still growing. Take a couple of minutes on requirements, then we will do some numbers, then the design. I will interrupt to keep us moving.

The clock

  • 4 min: functional requirements and scope
  • 4 min: non-functional requirements, with numbers
  • 5 min: back-of-envelope estimates
  • 16 min: high-level design and one or two flows
  • 16 min: deep dives and the close

Move them on out loud when a section overruns. The commonest failure is spending twenty minutes on requirements and never reaching a deep dive, and preventing that is your job as much as theirs.

Requirements · 8 min

Listen for: a scoped set of capabilities, an explicit out-of-scope list, and numeric targets rather than adjectives. Prompt with “what are you not building?” if they never scope, and “what number would make that requirement real?” if they say “fast” or “highly available”.

Functional (6)
  • Communities and posts: Users subscribe to subreddits and submit link, text or image posts to them.
  • Nested comments: Comments reply to the post or to other comments, to any depth. Threads can grow to tens of thousands of comments.
  • Votes: One up or down vote per user per post or comment, changeable. Scores are shown everywhere.
  • Ranking: Posts by hot, new, top and rising; comments by best, top, new and controversial.
  • Home and subreddit listings: A subreddit’s front page, and a home feed merging the user’s subscriptions.
  • Out of scope: Chat, ads, search, media hosting beyond a CDN, and moderation tooling beyond removal.
Non-functional (6)
  • Read-heavy (~26 k thread views/s): Hundreds of reads per write. Most traffic is people reading popular threads, much of it logged out.
  • Thread latency (p99 < 500 ms for the first page of a thread): This is the requirement that drives the hardest trade-off: a thread is a tree of thousands of comments sorted by a score that changes with every vote, and it must render fast while it is still growing.
  • Vote volume (~17 k votes/s avg, hot threads 10 k/s each): Votes are the most frequent write, and they concentrate on whatever is popular right now.
  • Freshness (new comments visible in seconds; scores within seconds to a minute): People reply to each other in real time; exact scores can lag.
  • Correctness of votes: One vote per user per item, idempotent under retries, and resistant to manipulation.
  • Availability (99.9 %): Degrade by serving cached threads and listings when writes or ranking lag.

Estimates · 5 min

Ask for two or three numbers, not all of them. What matters is whether they state assumptions, round sensibly, and say what the number implies. Push once with “where did that come from?”

The numbers (6)
  • Thread views per second: ~26 k. 75 M DAU × 30 thread views a day = 2.25 B/day ÷ 86 400 ≈ 26 k/s. Logged-out views are a large share and can be served from the CDN.
  • Votes per second: ~17 k. 75 M × 20 votes a day = 1.5 B/day ÷ 86 400 ≈ 17 k/s. A front-page thread can take 10 k/s on its own.
  • Comments per second: ~65. 5.5 M comments a day ÷ 86 400 ≈ 64/s. Writes are tiny next to reads; the cost is keeping trees and caches current.
  • A big thread: ~50 k comments · ~25 MB. 50 k comments × ~500 B each ≈ 25 MB. Far too much to send to a browser; the first page shows ~200 comments and "load more" links.
  • Vote records per year: ~550 B. 1.5 B votes a day × 365 ≈ 550 B (user, item, direction) records a year, at ~20 B each ≈ 11 TB. Stored so a user sees their own votes and cannot vote twice.
  • Subreddit listings: ~100 k active. About 100 k subreddits with recent activity, each with hot, new, top and rising listings of the top ~1,000 post ids. Small enough to precompute and keep in memory.

High-level design · 16 min

Let them draw. Interrupt only to ask what backs a component or what a box actually does. Then pick one flow below and ask them to walk it end to end.

Components (15)
  • Browser / app: Reads listings and threads, votes, and comments. Logged-out users get cached pages; logged-in users get personal overlays (their own votes, collapsed comments).
  • CDN (logged-out pages): Caches whole pages and API responses for logged-out users for tens of seconds. A large fraction of all reads never reaches the origin.
  • API gateway: Authentication, rate limits on votes and comments (spam and brigading), and routing.
  • Listing service: Serves subreddit front pages from precomputed listings and builds the home feed by merging the listings of the user’s subscribed subreddits.
  • Listings (per subreddit × sort): Top ~1,000 post ids per subreddit for hot, new, top and rising, kept in memory and updated by the ranking jobs.
  • Ranking jobs (hot · best): Consume vote and comment events, recompute hot scores for posts and best scores for comments, and update listings and thread sort orders.
  • Comment service: Writes comments with their place in the tree and builds the rendered view of a thread: sorted, truncated by depth and count, with "load more" markers.
  • Comment store (partitioned by post · path ordered): All comments of a post in one partition, each with its parent and a materialised path, so a whole thread (or a subtree) is one range read.
  • Thread cache (post × sort → rendered tree): The first page of each popular thread per sort order, ready to serve. Patched on new comments, rebuilt on a short timer as scores change.
  • Vote service: Records one vote per (user, item), handles changes and retractions, and emits score deltas. Never updates a score synchronously on the hot item itself.
  • Vote store (key = (user, item)): Every user’s vote on every item, for "did I vote?" overlays and to make voting idempotent. Partitioned by user, with a per-item index for recounts.
  • Score counters (sharded per hot item): Up and down counts per item, updated from vote deltas and split across sub-counters for hot items. Snapshotted to the item rows periodically.
  • Post service: Submits and serves posts; media is uploaded to object storage behind the CDN.
  • Post store: Posts with subreddit, author, title, body or link, score snapshot and comment count. Sharded by post id.
  • Events (Kafka): Votes, comments and posts as events, driving ranking, counters, notifications and anti-manipulation detection.
Flows to ask them to walk (5)
  1. Open a popular thread: The main read path: logged-out readers hit the CDN, logged-in readers hit a cached rendered tree with their own votes overlaid; only a miss reads the comment store.
    1. A logged-out reader’s request is answered by the CDN.
    2. A logged-in reader’s request goes to the comment service, which reads the cached first page for this sort.
    3. On a miss, it reads the post’s comments in path order from one partition and builds the tree.
    4. It overlays the reader’s own votes on the visible comments.
    5. Expanding "load more" fetches one subtree by path prefix.
  2. Reply to a comment: A small write that must appear in seconds: insert with a path, patch the cached tree, and let counts and ranking catch up asynchronously.
    1. The user posts a reply to a comment.
    2. The comment is inserted with a path built from its parent’s path.
    3. The author sees the reply immediately; the cached tree is patched.
    4. A comment-created event updates counts, ranking and notifications.
  3. A thread hits the front page: The scale case: 10 k votes a second on one post, thousands of new comments, and millions of readers. Every write path is absorbed asynchronously; reads come from caches.
    1. Votes on the post and its top comments arrive at 10 k/s.
    2. Score deltas are aggregated into sharded counters.
    3. Comment sort orders are recomputed on a short timer, not on every vote.
    4. Readers are served from the CDN and the thread cache.
    5. Scores are snapshotted to the post row every few seconds.
  4. Change a vote, twice, with a flaky connection: The correctness path: one vote per user per item, idempotent under retries, and counts that converge to the truth.
    1. The user upvotes, then switches to a downvote; the app retries both because of a bad connection.
    2. The vote store upserts the (user, item) row and returns the previous direction.
    3. The delta flows to the counters.
    4. A nightly job recounts recently active items from the vote store.
  5. Hot listings and the home feed: The background path: ranking jobs keep each subreddit’s listings current, and the home feed is a merge of the subscribed listings at read time.
    1. Ranking jobs consume vote and comment events and recompute post hot scores.
    2. Each subreddit’s hot, new, top and rising listings are updated in memory.
    3. A user opens the home feed; the listing service merges their subscriptions’ listings.
    4. Post details are hydrated from the post store.

Deep dives · 16 min

Pick two. Ask the headline question, let them answer, then use the follow-ups. The follow-ups are where the level gets decided, so leave time for at least three of them.

Storing a comment tree

Ask: Comments nest to any depth. How do you store them so a whole thread, or one subtree, loads in one query?

Good answers name: Materialised path per comment, all comments of a post in one partition, ordered by path, Adjacency list with recursive queries, Closure table (every ancestor–descendant pair), Nested sets (left/right numbers).

Our pick: Store comments in a table partitioned by post id with (post_id, path) as the clustering key. The path is the parent’s path plus a fixed-width, sortable segment for the new comment (base-36 of a per-post counter), so siblings sort by creation and every subtree is a contiguous prefix range. The comment service reads a thread with one range scan, builds the tree in memory, sorts each level by the requested order, and cuts it to a page with "load more" stubs. Parent id is stored as well for convenience. Reddit itself has long rendered threads by building trees in the application from comment lists, with heavy caching on top.

  1. A thread has 50,000 comments. Do you really read them all on a cache miss?
    For the first page of a sort like "best", yes, but rarely: the result is cached and rebuilt on a timer, so the full read happens a few times a minute for a hot thread. For "new" you can read the newest comments by time index instead. Storing a precomputed sort key per comment can also let the read stop early.
  2. How do you show a deleted comment that has replies?
    Keep the row with a "deleted" flag and blank body, so the path and the replies under it still render ("[deleted]"). Removing the row would orphan its subtree.
  3. Why fixed-width path segments?
    So paths sort correctly as strings: "0002" before "0010", whereas "2" sorts after "10". Fixed width (or a length prefix) keeps path order equal to tree order.
  4. How deep can it go?
    Storage allows any depth, but the renderer caps visible depth (say 8 to 10) and shows "continue this thread" beyond it, which opens the subtree as its own page. Very deep paths are just longer strings.
Counting votes

Ask: A front-page thread gets 10,000 votes a second. How do you record them and keep scores right?

Good answers name: Upsert per (user, item) as the truth; score deltas aggregated asynchronously into sharded counters; periodic recount, Increment the item row on every vote, Count votes only, no per-user records.

Our pick: Votes are stored as one row per (user, item) with the direction, partitioned by user. A vote request states the desired direction, and the upsert returns the previous direction, so the score delta is computed exactly and retries produce zero deltas. Deltas go through Kafka to counter consumers that batch per item per second and apply to sharded counters for hot items. Scores are snapshotted onto post and comment rows every few seconds for listings and cold reads. A nightly recount from the vote store corrects drift and applies anti-manipulation decisions. Displayed scores on very popular items are lightly fuzzed, as Reddit does, which makes vote-bot feedback harder to read.

  1. How do you detect vote manipulation?
    From the event stream: clusters of accounts that vote on the same items within seconds, new accounts voting in bursts, votes from the same IP or device ranges. Suspect votes are kept but excluded from counts; the recount applies the exclusion, so manipulation stops paying off.
  2. Why store votes by user rather than by item?
    The common reads are "what did this user vote on these 200 visible items" (to highlight arrows), which is one partition read by user. Counting per item is done from the delta stream, and recounts use a secondary index or a batch scan by item.
  3. Why does Reddit fuzz vote counts?
    Bots and spammers test whether their votes count by watching the score. Adding small noise to displayed counts on busy items hides the effect of individual votes, while the ranking uses the true values.
  4. A user deletes their account. What happens to their votes?
    Their vote rows are deleted and a recount job adjusts the totals of the items they voted on, by emitting negative deltas or recounting those items. Because votes are the source of truth, this is mechanical rather than a manual correction.
Hot and best

Ask: How do "hot" for posts and "best" for comments work, and why not just sort by score?

Good answers name: Hot: log of net score plus age; Best: lower bound of the Wilson confidence interval on the upvote ratio, Sort by net score, Learned ranking model.

Our pick: For posts, hot = sign(s) × log10(max(|s|, 1)) + (created_seconds − epoch) / 45000, where s is the net score: each tenfold increase in votes is worth about 12.5 hours of recency, so newer posts rise and every post sinks over time on its own. Because the time term only grows, scores never need recomputing just because time passed. For comments, best = the lower bound of the Wilson score interval for the proportion of upvotes, which is low when there are few votes and approaches the true ratio as votes accumulate. Both are computed by ranking jobs from counter values and stored with the item, so sorting is cheap.

  1. Why does hot never need a timer to decay posts?
    Because instead of subtracting age from old posts, it adds creation time to new ones. Every new post starts higher than every old one by the time elapsed, so old posts fall relative to new ones without anyone touching them.
  2. What does "controversial" mean computationally?
    High total votes with a ratio near 50/50: for example (ups + downs) raised to the power of the balance, where balance is the smaller count divided by the larger. Items with many votes split evenly rank first.
  3. How would you rank the home feed if subscriptions vary wildly in size?
    Normalise per subreddit (cap posts per subreddit per page, or compare scores relative to the subreddit’s typical score) so a small community’s best post can appear next to a huge community’s. Otherwise the biggest subreddits fill every page.
  4. Should hot use upvotes minus downvotes or the ratio?
    Hot uses the net score, so heavily downvoted posts sink. Best uses the ratio with a confidence bound because comments compete within one thread where exposure differs; net score there would mostly reward being early.
Keeping hot threads fast while they change

Ask: Popular threads change every second with new comments and votes. How do you cache them without serving stale or broken trees?

Good answers name: Cache rendered first pages per (post, sort); patch on new comments; rebuild on a short timer for score changes; coalesce rebuilds; CDN for logged-out, Invalidate on every change, Long fixed TTL only.

Our pick: The comment service caches the rendered first page of each thread per sort order. A new comment is inserted into the cached tree under its parent (or its parent’s "load more" count is incremented) immediately. Score changes do not invalidate; instead hot threads are rebuilt every few seconds by the ranker with fresh scores, with a single-flight lock so concurrent misses trigger one rebuild. Personal state (own votes, collapsed comments) is overlaid per request and never cached in the shared tree. Logged-out pages are cached at the CDN for 30 to 60 seconds.

  1. A moderator removes a comment. How fast does it disappear?
    Removal is an explicit invalidation, not a timer: the comment is marked removed in the store and the cached trees and CDN entries for that thread are purged at once. Moderation actions are rare enough to invalidate eagerly.
  2. What is single-flight and why does it matter here?
    When the cache entry for a hot thread expires, thousands of requests miss at once. Single-flight lets the first build and makes the rest wait for its result (or serve the slightly stale copy), so the comment store sees one read instead of thousands.
  3. How do you avoid sending the same 200 comments to someone who refreshes?
    Use conditional requests: the cached page carries a version, and the client sends it back; if nothing changed, the server answers 304. For live threads, a lightweight 'new comments since' endpoint lets the client fetch only additions.
  4. How big is the thread cache?
    Only threads read recently are cached, at a few hundred KB per rendered page per sort. Tens of thousands of active threads are a few gigabytes, which fits a modest cache cluster; cold threads are rebuilt on demand.
Front pages and the home feed

Ask: How do you build a user’s home feed from the hundreds of subreddits they follow?

Good answers name: Precomputed per-subreddit listings; merge subscriptions at read time with per-subreddit caps, Fan-out on write to per-user feeds, Query posts across subscribed subreddits on every request.

Our pick: Ranking jobs maintain, for each active subreddit, sorted sets of the top ~1,000 post ids for hot, new, top and rising, updated as votes arrive. A subreddit page reads its listing directly. The home feed reads the top of each subscribed subreddit’s hot listing (with a cap for users subscribed to hundreds), merges by score with a per-subreddit limit per page, and caches the merged result per user for a minute for pagination. Hydration reads post rows by id from a post cache. This is fan-out on read, the opposite choice from Design a News Feed, because here the number of sources is small and their audiences are huge.

  1. A user is subscribed to 800 subreddits. Does every home page read 800 listings?
    No: cap the merge at the most active few hundred subscriptions, weighted by how often the user engages with each, and cache the merged feed for pagination. Rarely visited subscriptions appear occasionally rather than every time.
  2. Why is fan-out on read right here when news feeds use fan-out on write?
    A news feed has as many sources as users, each with a small audience, so pushing is cheap per post and pulling would mean merging hundreds of user timelines. Reddit has relatively few sources with enormous audiences, which flips the cost: pulling from shared per-subreddit listings is cheap, pushing to millions of feeds is not.
  3. How does 'rising' work?
    It favours posts gaining votes quickly relative to their age, computed from the recent vote rate rather than the total. It needs the delta stream, which the ranking jobs already consume, so it is a different score over the same events.
  4. What happens to listings when a post is removed?
    The removal event deletes the post id from every listing it is in, and the merged home feeds that are cached for a minute expire quickly. Hydration also skips removed posts, so a stale id never renders.

Close · 5 min

Ask what breaks first at ten times the load, and what they would build next. Then give them your read: one thing that was strong, one thing that was missing, one thing to practise. Be specific; “good job” helps nobody.

Open in your browser to sign in

Google does not allow sign-in inside this app's built-in browser. Open this page in Safari and sign in there. The link opens this same page.

Tap the ⋯ or share button at the top or bottom of the screen, then Open in browser. Or copy the link and paste it into Safari.