Design Reddit

MediumSocial & FeedsVotingRankingComment TreesCaching

Problem

Design a community discussion site. Users join communities (subreddits), create posts (text, link, or image), and upvote or downvote posts and comments. Each community shows its posts sorted by hot, new, and top, and each post has nested comment threads that can go many levels deep. Users also get a home page combining posts from the communities they joined. The site has 50 million daily active users, 100,000 active communities, about 1 million posts and 10 million comments per day, and roughly 100 million votes per day.

  • How should "hot" be defined: a decay of score over time, and how quickly should rankings update after votes?
  • How deep can comment threads go, and how many comments can a single post have?
  • Should exact vote counts be shown, or fuzzed to discourage manipulation?
  • Is the home page built from joined communities only, or does it include recommendations?
  • What moderation tools are in scope: removing posts, banning users, automated filters?
  • What is the read to write ratio: mostly logged-out readers, or active participants?

Items in bold are existing requirements. The rest are suggestions. Confirm with the interviewer whether they are relevant first.

  • Create communities and join or leave them.
  • Create text, link, and image posts in a community.
  • Upvote and downvote posts and comments, one vote per user per item.
  • List a community's posts by hot, new, and top (day, week, all time).
  • Show nested comment threads on a post, sorted by best, top, or new.
  • Show a home page of posts from joined communities.
  • Let moderators remove posts and comments and ban users from a community.

Items in bold are existing requirements. The rest are suggestions. Confirm with the interviewer whether they are relevant first.

  • 100 million votes per day is about 1,200 per second average, with spikes of 10,000 per second on viral threads.
  • Reads dominate: about 50 million DAU x 30 page views = 1.5 billion page views per day, near 17,000 per second average and 50,000 at peak.
  • Listing pages and comment pages load in under 300 ms; most logged-out traffic is served from cache.
  • Hot rankings reflect new votes within about a minute.
  • Storage: 11 million posts and comments per day at about 1 KB is 11 GB per day, about 4 TB per year.
  • Votes must not be double-counted; scores may be eventually consistent.

Posts, comments, and votes are stored in a sharded relational database, with each vote recorded per user and item so it is idempotent, and each item's score kept as a denormalized counter updated asynchronously from a vote stream. A ranking worker recomputes hot scores and maintains precomputed sorted listings per community in Redis, which serve community pages and are merged for each user's home page. Comment trees are stored with a parent pointer and materialized path, loaded per post, sorted, and cached, while logged-out page views are served almost entirely from the CDN.

Loading diagram...

Create a post. A user submits a post to a community. The post service checks the community's rules and ban list, writes the post, and inserts its ID into the community's new listing and, with an initial score, into its hot listing.

Vote. A user upvotes a post. The vote service upserts (user_id, item_id) -> +1 in the vote store. If the previous value was 0 or -1, it emits a vote event with the delta (+1 or +2). Repeated clicks produce no delta, so votes are idempotent.

Re-rank. Score workers consume vote events, batch deltas per item for a few seconds, and apply them to the item's ups and downs counters. For posts, they recompute the hot score and update the post's position in the community's hot and top sorted sets.

Read a community. GET /r/programming/hot reads the top 25 IDs from the hot sorted set, hydrates posts from a post object cache, and overlays the viewer's own votes. Logged-out responses are cached at the CDN for 30 to 60 seconds.

Read comments. Opening a post loads its comment tree from the tree cache (or builds it from the database), sorts each level by the chosen order, and returns the first 200 comments with "load more" stubs for the rest.

Records each user's vote and produces score deltas.

  • Cassandra or sharded PostgreSQL for vote records; Kafka for vote events.

API

  • POST /vote with { itemId, direction: 1 | 0 | -1 } returns 204.

Data Model

  • votes(item_id, user_id, direction, updated_at) partitioned by item_id, plus user_votes(user_id, item_id, direction) for showing a user's votes on a page.

Scalability

A vote is one upsert and one event. Viral posts concentrate votes on one item, so counters are not incremented in the database on each vote; score workers aggregate deltas in memory and apply them in batches.

Tradeoffs

Recording every individual vote costs storage (about 36 billion rows a year) but is required for idempotency, for letting users change their vote, and for detecting vote manipulation. Old votes on archived posts (after 6 months, voting closes) can move to cold storage.

Maintain item scores and the sorted listings for each community.

  • Kafka consumers; Redis sorted sets for listings.

Data Model

  • listing:{community}:hot, listing:{community}:new, listing:{community}:top:day|week|all, each a sorted set of post IDs capped at 1,000 entries.
  • Hot score: sign(s) * log10(max(abs(s), 1)) + created_epoch_seconds / 45000, where s = ups - downs.

Scalability

The hot formula depends only on score and creation time, so a post's hot value changes only when it is voted on; there is no need to recompute every post as time passes, because newer posts naturally carry a larger time term. Listings are capped so each sorted set stays small.

Tradeoffs

Using the logarithm of the score means the first 10 votes count as much as the next 100, giving new posts a chance against established ones. Precomputing listings costs memory but turns every listing page into one sorted set range read; computing ORDER BY hot in SQL over a community's posts on every page view would not scale.

Serves community pages and builds each user's home page.

  • Stateless service reading Redis listings and a post object cache in Memcached.

API

  • GET /r/:community/:sort?after=&limit=25 returns posts.
  • GET /home?after= returns the home page for the signed-in user.

Scalability

The home page merges the top 50 hot entries from each of the user's joined communities (capped at the 100 most active joined communities) and takes the top 25 by hot score. The merged result is cached per user for a minute.

Tradeoffs

Merging at read time is used rather than fanning out posts to every member, because large communities have tens of millions of members and posts' hot positions change constantly, which would require continual rewrites of every member's home page.

Stores comments and serves nested threads.

  • PostgreSQL sharded by post ID; Redis for cached, pre-sorted comment trees.

Diagram

Loading diagram...

Data Model

  • comments(comment_id, post_id, parent_id, path, author_id, body, ups, downs, created_at, removed) with an index on (post_id, path).

Scalability

Sharding by post_id keeps a whole thread on one shard, so building a tree is one indexed range read. Threads with tens of thousands of comments are served in pages: top-level comments first, deep branches loaded on demand.

Tradeoffs

A materialized path (for example 0001.0004.0002) makes subtree loads a prefix range query, while the parent pointer keeps moves and inserts simple. A recursive query over parent pointers alone works but is slower for deep threads. The "best" sort uses the lower bound of a confidence interval on the upvote ratio, which favors comments with many votes over ones with a lucky few.

Stores posts, communities, memberships, and moderation state.

  • Sharded PostgreSQL with read replicas; S3 and a CDN for images.

Data Model

  • communities(id, name, rules, created_at), memberships(user_id, community_id).
  • posts(post_id, community_id, author_id, type, title, body, url, media_ref, ups, downs, created_at, removed) sharded by community_id.
  • bans(community_id, user_id, expires_at).

Tradeoffs

Sharding posts by community keeps community-scoped queries and moderation local to one shard, at the risk of uneven shard sizes for the largest communities, which are given dedicated shards.

Related questions