Reddit System Design Interview: Comment Trees, Vote Ranking, and Cached Listings at Scale
Reddit is one of the most read-heavy consumer sites on the internet. The company has publicly described billions of monthly visits and tens of millions of daily active users, with a single popular thread able to draw hundreds of thousands of comments and votes in hours. The hard part is not storing a post. It is rendering a deeply nested comment tree, ranking it correctly, counting votes as they pour in, and serving the same viral listing to millions of readers without melting the database. All raw traffic figures here are estimates unless linked to a published source.
A Reddit clone looks simple until you open a busy thread. The core objects are posts, comments, votes, and subreddits, but comments form arbitrarily deep trees that must be stored, ranked, and paginated with a working 'load more comments' path. Ordering is driven by three real algorithms: hot uses a logarithmic vote weight plus a time term so fresh content floats up, best uses the Wilson score lower confidence bound so a comment with 10 of 10 upvotes does not outrank one with 400 of 420, and controversial rewards posts where ups and downs are close and both large. Votes arrive faster than any single Postgres row can absorb, so counting is asynchronous and cached, and Reddit historically fuzzed vote totals to frustrate spam bots. Reads dominate writes by a wide margin, so subreddit and home listings are precomputed and cached rather than queried live. The interesting failure mode is a hot post: a single key gets so much traffic that a cache miss can stampede the origin. Reddit's real history, a schemaless 'thing' plus 'data' store on Postgres, heavy Cassandra use, memcached everywhere, and RabbitMQ for async work, gives you a concrete blueprint to reason from.
Asked at: Asked at Reddit, Meta, Quora, Discord, and many companies that build social or discussion products. It also shows up whenever an interviewer wants a feed or forum design that goes deeper than a flat timeline.
Why this question is asked
Interviewers like Reddit because it forces you off the happy path of a flat feed. The comment tree tests whether you understand recursive data structures in a relational store, and the ranking piece tests whether you can reason about a real math formula instead of hand waving about 'sorting by score'. Voting at scale exposes write amplification and counting under contention, while the listing and viral post scenarios test caching, precomputation, and stampede control. A strong candidate connects each subsystem to the read-heavy nature of the workload and defends why some things are eventually consistent while others are not.
Requirements
Always clarify these in the first 5 minutes of the interview. Do not start drawing boxes until both lists are agreed.
Functional requirements
- Users can submit posts (link or text) to a subreddit and comment on posts
- Comments can reply to other comments, forming arbitrarily deep threaded trees
- Users can upvote and downvote posts and comments, changing or removing their vote
- Sort a post's comments by best, top, new, and controversial
- Sort a subreddit and the home feed by hot, top, new, rising, and controversial
- Load a truncated comment tree with a working 'load more comments' expansion
- Show per-user personalized home feed based on subscribed subreddits
- Support moderation actions: remove, lock, sticky, and distinguish content
- Search posts and comments by keyword within and across subreddits
Non-functional requirements
- Read-heavy: reads vastly outnumber writes, so optimize the read path first
- Listing and thread pages should feel instant, low tens of milliseconds from cache
- Vote counts can be eventually consistent and slightly fuzzed without hurting UX
- A single viral post must not overload the database (stampede protection)
- Highly available; a stale listing is acceptable, an error page is not
- Ranking must be deterministic and cheap to recompute as votes change
- Horizontally scalable storage for votes and comments as data grows without bound
Back-of-envelope scale estimates
Show your math. Pulling numbers from thin air signals you have not thought about the load.
Daily active users
~50 million (estimate)
Reddit has publicly cited figures in this range in filings and posts. Treated as an order-of-magnitude anchor, not an exact live number.
Read to write ratio
~100:1 or higher (estimate)
Most sessions are lurkers scrolling listings and reading threads. For every post or comment written, hundreds of page and API reads occur, which is why precomputed cached listings dominate the design.
Votes per second (peak)
~10,000+ per second (Derived estimate)
If 50M DAU cast an average of ~20 votes per day, that is ~1B votes/day, roughly 11,600/second averaged, with peaks several times higher during busy hours. Derived, not published.
Comments on a viral thread
100k+ comments (observed)
Large AMA and event threads routinely exceed six figures of comments, which is what makes tree pagination and 'load more comments' a real engineering problem rather than a UI detail.
Hot listing recompute cost
O(1) per vote (design target)
The hot score is a pure function of ups, downs, and post age, so each vote updates one score and the post's position in a cached sorted set. No full scan of the subreddit is needed.
Comment storage growth
Billions of rows, append-heavy (estimate)
Comments are created constantly and rarely deleted, so the store must shard and grow without bound. This is a large part of why Reddit leaned on Cassandra for this class of data.
High-level architecture
A request for a subreddit page hits a load balancer (Reddit historically used HAProxy) and lands on an application server. The app first tries memcached for the rendered listing or the precomputed list of post IDs for that subreddit and sort. On a hit it hydrates the post objects, again mostly from cache, and returns. On a miss it reads from the primary datastore, builds the listing, and writes it back to cache. Opening a post loads the post object plus its comment tree; the tree is fetched as a set of comment rows keyed by post, assembled into parent and child links in the application, sorted by the chosen algorithm, then truncated so only the top slice of each branch is sent, with collapsed branches represented as 'load more' stubs. Writes take a different path. Submitting a post or comment, or casting a vote, is accepted quickly and the expensive follow-up work (updating vote tallies, recomputing affected listings, notifying, spam checks) is pushed onto a RabbitMQ queue and handled by background workers. This keeps the synchronous write cheap and lets counting and ranking catch up asynchronously. Underneath, Reddit's classic design stored core objects in a schemaless 'thing plus data' model on Postgres, used Cassandra heavily for high-volume data like votes and comments, and relied on memcached as the glue that made the read-heavy workload survivable.
In a real interview, sketch this on the whiteboard before diving into any single box.
Core components
Walk through each service. The interviewer wants to hear what each one owns, not just the names.
API and application servers
Stateless app servers behind a load balancer handle rendering, tree assembly, and ranking. Reddit ran a famously monolithic Python application rather than many microservices. Keeping servers stateless lets you scale reads horizontally and route any request to any box.
Thing and Data store (Postgres)
The historical core is a schemaless entity model: a 'thing' table holds fixed columns like id, type, and vote counts, and a 'data' table holds arbitrary key value attributes joined by thing id. This gave schema flexibility for Links, Comments, Accounts, and Subreddits without migrations, at the cost of expensive joins that must be cached aggressively.
Cassandra cluster
Cassandra stores high-volume, append-heavy data such as votes and comment data where its sharding and failure tolerance shine. Its tunable consistency and write throughput fit vote and comment ingestion better than a single relational primary. New features often landed on Cassandra first.
Memcached tier
Memcached is used everywhere: cached listings, rendered fragments, memoized function results, and object lookups. Almost every read tries cache before touching a database. It is the single most important reason a read-heavy site with a schemaless EAV store stays fast.
RabbitMQ and async workers
User actions that are expensive, like vote tallying, listing recomputation, and spam processing, are deferred to queues consumed by background workers. This decouples the fast write acknowledgment from the slow bookkeeping and smooths out spikes when a post goes viral.
Precomputed listing cache
Subreddit and home listings for each sort (hot, new, top, controversial) are stored as cached, sorted lists of post IDs rather than computed per request. When a vote changes a score, a worker updates the relevant cached lists so readers get an already ranked answer.
Vote counter and fuzzer
Votes are aggregated asynchronously into per-thing up and down counts. Displayed totals were historically fuzzed, deliberately perturbed, to make it harder for spammers to confirm whether their bot votes were counted, while the internal score used for ranking stayed accurate.
Data model
Pick the right store per table. Justify each choice with the access pattern, not by reflex.
thingthing_idthing_typeupsdownscreated_utcdeletedspamThe fixed-column core row for every object (link, comment, account, subreddit). Vote counts live here so ranking can read them without a join. Type plus id identifies the entity.
datathing_idkeyvalueThe flexible EAV half of the model. Attributes like title, url, body, or author are rows keyed by thing_id, joined back to thing. Schemaless, migration free, and heavily cached to hide join cost.
commentcomment_idpost_idparent_idauthor_idbodycreated_utcA comment references its post and its parent comment. parent_id null means top level. The tree is reconstructed in the app from these adjacency links; storing parent_id (adjacency list) is simple to write, while a materialized path column would speed subtree reads.
voteuser_idthing_iddirectioncreated_utcOne row per user per votable thing, direction in {up, none, down}. Kept in Cassandra for write throughput. Changing a vote is an upsert; aggregate ups and downs are recomputed asynchronously rather than read by scanning this table.
listing_cachesubreddit_idsortpost_idscorerank_positionLogical view of the precomputed sorted listings, typically materialized in memcached or a sorted set rather than a live table. Updated by workers when scores change so reads never rank on the fly.
subscriptionuser_idsubreddit_idcreated_utcWhich subreddits a user follows, used to assemble the personalized home feed by merging the top of each subscribed subreddit's hot listing.
Deep dives
These are the conversations the interviewer is steering you toward. Practice each one until you can talk through it without notes.
Storing and rendering the comment tree
Comments form a tree of arbitrary depth, and the storage choice shapes everything. The simplest model is an adjacency list: each comment stores parent_id, which is trivial to insert but requires reading the whole thread and rebuilding the tree in memory to render it. A materialized path stores the full ancestry as a string like 4.19.230, which makes fetching an entire subtree a single prefix range query and makes depth obvious, at the cost of longer keys and rewrites on moves. A closure table stores every ancestor descendant pair for fast queries at higher write and storage cost. Reddit's practical answer is to load the comment rows for a post, assemble the tree in the application, sort each sibling group by the chosen algorithm, then serialize only a truncated slice. Deep or low-ranked branches are replaced by 'load more comments' stubs that carry the parent id and a count, so expanding them is a targeted follow-up fetch rather than shipping 100k comments at once.
The hot ranking formula
Hot ranking is a published, deterministic function. Let s = ups - downs, and sign be 1, 0, or -1 for positive, zero, or negative s. The score is sign * log10(max(|s|, 1)) + (created_epoch_seconds - 1134028003) / 45000, rounded to 7 places. Two ideas matter. The log term means the first 10 upvotes count as much as the next 100 and the next 1000, so early votes move a post a lot and later votes move it little. The time term is additive and grows with the post's submission time, so newer posts start with a higher base; the 45000 second divisor sets how fast fresh content decays past older content. Because 1134028003 (a fixed 2005 epoch reference) is a constant, the formula is stable and each vote only recomputes one post's score, which is why hot listings can be maintained incrementally in a cache rather than recomputed by scanning a subreddit.
Best comment sorting with the Wilson score
Sorting comments by raw score is wrong because it rewards volume over quality and buries good new comments. Reddit's best sort instead uses the lower bound of a Wilson score confidence interval on the fraction of upvotes. You treat the votes as a sample: p is the observed positive fraction, n is the total votes, and z is the normal quantile for the confidence level. The lower bound answers 'given the votes so far, what is a statistically cautious estimate of this comment's true approval'. A comment with 1 upvote and 0 downvotes has p = 1 but tiny n, so its lower bound is modest, while a comment with 400 up and 20 down has slightly lower p but far more evidence and ranks above it. This gives new but promising comments a fair chance while not letting a single lucky vote top the thread. The formula is cheap enough to compute per comment and cache alongside the vote counts.
Controversial ranking
Controversial surfaces posts and comments that split the room. The intuition is to reward high total engagement combined with a near even up down balance, so a comment with 500 up and 480 down scores high while 1000 up and 5 down does not. A common formulation multiplies the total vote magnitude by a balance factor: something like (ups + downs) raised to a power that shrinks as the ratio of ups to downs (or its inverse) moves away from 1. The exact exponent is a tuning knob, but the shape is what matters in an interview: two terms, one that grows with total votes and one that peaks when ups roughly equal downs. Like hot and best, it is a pure function of the two counts, so it slots into the same asynchronous 'recompute one score, update the cached listing' machinery.
Voting at scale and write amplification
A single popular post can attract tens of thousands of votes in minutes, and every vote wants to touch the same post's counters. If you naively UPDATE one row per vote you create a contention hot spot and lock contention on that row. Reddit's approach is to make the vote itself a cheap, idempotent-ish upsert of the user's vote row, then aggregate counts asynchronously through a queue. Workers batch many votes and apply them to the aggregate up and down counts, so the write path to the hot counter is coalesced rather than serialized per vote. Displayed totals were historically fuzzed, shown slightly off from the true number, so vote-manipulation bots could not verify whether their votes registered, while the ranking used the real counts. Vote data itself lived in Cassandra, whose high write throughput and tunable consistency suit a firehose of small writes better than a single relational primary.
Precomputed listings and the read-heavy path
Because reads dwarf writes, ranking a subreddit on every page view would be wasteful and slow. Instead each subreddit and sort combination is maintained as a precomputed, cached list of post IDs. When a vote changes a post's score, a background worker recomputes that post's rank and updates the affected cached lists, so a reader gets an already ordered answer and only needs to hydrate the post objects, themselves cached in memcached. The home feed is assembled per user by merging the tops of the hot listings of their subscribed subreddits, which keeps personalization cheap because the heavy ranking work is shared across all users of a given subreddit. This is a classic write-time-work versus read-time-work trade: you pay a little extra on each vote to make the vastly more common read nearly free.
Hot post cache stampede and the thundering herd
The scariest failure is a viral thread. Its listing and comment pages are requested by millions, so they live in cache, but when that cache entry expires or is invalidated, thousands of concurrent requests can miss at once and all try to rebuild it from the database simultaneously. That thundering herd can overwhelm the origin and cause a cascading outage precisely for the most popular content. Defenses include request coalescing so only one rebuild runs while others wait on it, a short lived lock or 'promise' per cache key, serving slightly stale content while a single background refresh happens (stale-while-revalidate), and adding jitter to TTLs so many keys do not expire together. For the very hottest keys you also want the object cached at multiple layers, including the CDN or edge, so most reads never reach the app at all.
Trade-offs to discuss
Every senior interviewer expects you to surface at least 3 of these. Pick the decisions, state the alternatives, and justify your choice.
Adjacency list versus materialized path for comment trees
Adjacency list (parent_id) is dead simple to write and matches how comments are created, but reading a subtree means fetching the thread and rebuilding it in memory. Materialized path makes subtree reads a single prefix query and makes 'load more' cheap, at the cost of longer keys and rewrites when structure changes. Reddit-style systems favor cheap writes plus in-app assembly and heavy caching.
Synchronous vote counting versus async aggregation
Counting each vote synchronously gives instant accurate totals but creates a write hot spot on popular posts and couples the user's action to slow bookkeeping. Async aggregation through a queue coalesces writes and keeps the vote fast, accepting that displayed counts lag slightly. For a social site, eventually correct counts are fine and the throughput win is large.
Wilson score best versus raw score for comments
Raw score is trivial to compute and easy to explain, but it buries good new comments and lets sheer volume win. The Wilson lower bound needs a bit more math yet produces fairer ordering that accounts for how much evidence each comment has. The added compute is negligible next to the ranking quality gain.
Schemaless thing/data model versus normalized relational schema
The EAV thing plus data model let Reddit add attributes without migrations and evolve fast in the early days, which suited a small team shipping features. The price is expensive joins and query patterns that only work because almost everything is cached in memcached. A fully normalized schema would query more cleanly but slow feature iteration.
Precomputed cached listings versus ranking on read
Ranking live on every request keeps data perfectly fresh but repeats expensive work for a read-heavy audience. Precomputing listings and updating them on vote events shifts work to write time, where it is far less frequent, and makes the common read nearly free at the cost of some staleness and extra worker complexity.
Vote fuzzing versus exact public counts
Showing exact totals is transparent and simplest, but it hands vote-manipulation bots a precise feedback signal to confirm their attacks. Fuzzing displayed counts denies that signal at the cost of small user-visible inaccuracy, while ranking still uses the true numbers. A reasonable anti-abuse trade for a public voting system.
How Reddit actually does it
Reddit open sourced much of its code, and its architecture is unusually well documented for a site of its size. The classic stack was a monolithic Python (Pylons) application behind HAProxy, a schemaless 'thing' and 'data' model on Postgres for core objects, Cassandra for high-volume data like votes and comments, memcached layered everywhere for listings, rendered fragments, and memoized lookups, and RabbitMQ for asynchronous jobs such as vote counting and listing updates. The ranking formulas (hot, the Wilson score confidence sort for best, and controversial) were published and widely reproduced. More recent Reddit engineering writing describes moving expensive work like voting and submission into async queues, using change data capture (Debezium) to keep caches and replicas consistent, and continuing to lean on precomputed listings and heavy caching to serve a read-dominated workload.
Lessons to study before this interview
If any of these topics are fuzzy, the interviewer will catch it. Each lesson is 15 to 60 minutes with diagrams, code, and a quiz.
Related system design interview questions
Practice these next. They lean on the same core building blocks as Reddit.