Skip to content
System Design

Learn

Design Reddit

Designing Reddit: the hot ranking formula, storing and paginating deeply nested comment trees, vote aggregation without hotspots, and subreddit-scoped feeds.

4 min readUpdated 2 Sept 2026

#Ranking#Trees#Voting#Caching

Reddit differs from other social systems in a useful way: **there is no follow graph to fan out.** Content lives in communities, and the feed is a ranked query over a community. That makes ranking and comment trees the substance of the answer.

Requirements

Functional

  • Post links or text to a subreddit; browse a subreddit ranked by hot/new/top.
  • Nested comment threads with unlimited depth.
  • Upvote and downvote posts and comments.
  • A personalised home feed merging the user's subscribed subreddits.

Non-functional

  • Feeds load fast; they are the landing page.
  • Vote counts can be eventually consistent and approximate.
  • Extremely read-heavy, with heavy skew toward large subreddits.
50M daily users, 100K subreddits
2M posts/day, 20M comments/day ≈ 250 writes/second
Feed reads ≈ 100,000/second

Ranking

No fanout means the feed is a ranked query per subreddit, precomputed and cached. The formula that made Reddit work:

score = log10(max(|ups - downs|, 1))
      + sign(ups - downs) × (seconds_since_epoch / 45000)

Two properties are worth explaining, because this is the part interviewers push on:

  • The logarithm means the first 10 votes move a post as much as the next 100 — early

votes matter enormously, later ones barely. Without it, a post with 10,000 votes would never be displaced.

  • The linear time term means a post's score rises with submission time regardless of

votes, so newer content continuously pushes older content down. Time is added, not used as a decay divisor, which makes ranking stable and cheap: **a post's score never changes unless its votes change.**

That last property is the design win. Scores can be stored in a per-subreddit sorted set (Redis) and updated only on a vote, so serving a "hot" page is a single range read.

The "best" comment sort is a different formula — the Wilson score confidence interval — which asks "given these votes, what is the lower bound on the true approval rate". It stops a comment with 3 upvotes and 0 downvotes outranking one with 300 and 50.

Comment trees

Unlimited nesting is the modelling problem. Options:

ApproachReadWriteNotes
Parent pointer onlyRecursive queriesTrivialSimple, slow reads
Materialised pathOne prefix queryEasypath = "/12/45/98/"
Nested setsFast subtree readsExpensive rewritesBad for constant inserts
Closure tableFast both waysExtra rows per edgeStorage-heavy

Materialised path is the usual answer. Each comment stores its ancestry as a string, so

an entire subtree is one indexed prefix query, and sorting by path yields the tree already in display order.

comment_id | path        | depth | score
1          | /1/         | 0     | 250
2          | /1/2/       | 1     | 90
3          | /1/2/3/     | 2     | 12
4          | /1/4/       | 1     | 45

Deep threads still need pagination: load the top-level comments plus a few levels, and render "continue this thread" links that fetch a subtree on demand. A post with 50,000 comments must never be a single response.

Votes without hotspots

A viral post takes thousands of concurrent votes. Incrementing a counter column makes that row a lock hotspot.

votes    user_id, thing_id, direction, created_at   (the source of truth,
                                                     also prevents double voting)
counts   thing_id → ups, downs                      (Redis, atomic INCR)
         periodically reconciled from the votes table

Keep the individual vote rows — you need them to prevent double voting and to allow changing a vote — but serve the aggregate from a fast counter. Displayed counts are deliberately fuzzed and eventually consistent, which is both an anti-manipulation measure and a licence to relax consistency.

The home feed

A user subscribes to, say, 50 subreddits. The home feed is a **merge of the top N from each subscribed subreddit's cached ranking**, re-sorted by score — the k-way merge pattern, and cheap because each subreddit's ranked list is already computed. Cache the merged result per user for a short window.

Storage

posts       post_id, subreddit_id, author_id, title, url/body, created_at, score
comments    comment_id, post_id, author_id, path, depth, body, created_at, score
votes       user_id, thing_id, direction
subs        user_id, subreddit_id
rankings    Redis sorted set per subreddit per sort order

Shard by subreddit_id — queries are almost always scoped to one community, so this keeps them single-shard. The risk is that a huge subreddit becomes a hot shard, mitigated by caching its ranked pages aggressively at the edge.

Follow-ups to expect

  • Vote manipulation — rate limits, account age and karma thresholds, and detecting

coordinated voting rings in the graph.

  • Moderation — per-subreddit moderators, removal queues, automated rules.
  • Search — a separate inverted index across posts and comments.
  • Controversial sort — high engagement with a near-even up/down split.
  • Editing and deleting — comments are tombstoned rather than removed, so the tree

structure below them survives.

  • Live threads — a WebSocket push for fast-moving discussions.

Track this problem on the System Design sheet.

Frequently asked

How does Reddit's hot ranking algorithm work?

It adds a logarithm of the vote score to a linear function of submission time. The log means early votes matter far more than later ones, so a post with 10,000 votes cannot dominate forever; the additive time term means newer posts continuously push older ones down. Because time is added rather than used as a decay factor, a post's score only changes when its votes change — so scores can live in a sorted set and be updated on vote alone.

How do you store deeply nested comment threads?

Materialised paths. Each comment stores its full ancestry as a string like /1/2/3/, so an entire subtree is a single indexed prefix query and sorting by path returns the tree already in display order. Deep threads are paginated with 'continue this thread' links that fetch a subtree on demand rather than returning fifty thousand comments at once.

How do you handle vote counts on a viral post?

Keep individual vote rows as the source of truth — they prevent double voting and allow vote changes — but serve the aggregate from an atomic counter in Redis, reconciled periodically. Incrementing a column on the post row would make it a lock hotspot. Displayed counts being approximate and eventually consistent is acceptable, and also makes manipulation harder to measure.

Related