SDSystem design
Start learning free

LESSON 91 OF 140 · PRACTICE PROBLEMS

SYSTEM DESIGN · INTERVIEW WALKTHROUGH

Design Nested Comments: System Design Interview Practice

The problem

"Design a comment system that other websites embed." A news site adds the widget under its articles, and its readers comment and reply to each other, nested to any depth. 50,000 sites use it, each with its own moderators and rules.

This practice problem reuses Reddit's comments. Five things are new: many sites in one system, threads hundreds of replies deep, a sort the reader picks at every level, deletes inside a tree, and moderators per site. Park reply emails for later.

The numbers

From the interviewer: every page view on those sites loads the widget, 500 million loads a day, and readers post 5 million comments a day. Round a day to 100,000 seconds (86,400 really); busy hours run about 5× the average.

  • Loads: 500 million ÷ 100,000 = 5,000 a second on average (about 5,800 with 86,400), so about 25,000 a second at busy hours.
  • New comments: 50 a second, 250 at busy hours: reads outnumber writes 100 to 1.
  • Storage: at about 500 bytes a comment, 2.5 GB a day, about 900 GB a year. A popular page with 20,000 comments is about 10 MB, too much to send at once.

Storing the tree: a path per comment

Three common ways, each with a cost:

  • Adjacency list: each comment keeps only its parent's id. Cheapest to write, and fine for loading a whole page at once, as in Reddit. But the thread under one comment takes a query per level, each waiting for the last: a thread 100 deep is 100 queries in a row. A recursive SQL query does the same walk inside the database.
  • Closure table: a row for every ancestor and descendant pair, with the depth between them. Any thread is one indexed query, but a reply 100 deep writes 101 rows, and one argument 300 replies long stores 45,150 pairs.
  • Materialized path: each comment keeps its path from the top, like c1/c2/c3. A reply writes one row: its parent's path plus its own id.

With reads 100 times the writes and deep threads, the path wins here. In an index on (site, page, path) a thread's comments sit side by side, so loading one is a single range scan, in reading order, and the depth is the number of steps. The costs: deep comments carry long paths (8-character ids and a slash make a comment 300 deep carry 2,700 bytes, so some designs cap how deep replies nest), and moving a comment would rewrite its subtree, which never happens here. Give all ids the same length, so text order is time order and c5 never matches c51.

Deep threads: continue this thread

No screen fits 300 levels of indent, so the widget shows 5 levels and, under a comment at depth 5 that has replies, a "continue this thread" link; a reply count kept on each comment says where it goes. Page p9's index, in path order:

path                    by
c1                      cy
c1/c2                   ana
c1/c2/c3                ben
c1/c2/c3/c4             ana
c1/c2/c3/c4/c5          ben
c1/c2/c3/c4/c5/c6       ana
c1/c2/c3/c4/c5/c6/c7    ben
c1/c2/c8                dee

The first screen reads the rows down to 5 steps (c1 to c5, and c8). "Continue this thread" under c5 asks for every path that starts with c1/c2/c3/c4/c5: c5, c6 and c7, three rows side by side, one range scan that stops at c1/c2/c8.

GET /comments?site=news.org&page=p9&sort=top   → the first 5 levels
GET /comments/c5/thread?sort=top                → c5, c6, c7

Sorting: top or new at every level

Path order puts each reply under its parent, with siblings oldest first. For top or new, the server sorts each comment's replies by score or by time while it builds the tree in memory. Sorting all of a page's comments by score in one query would pull replies away from their parents: if c8 scores higher than c2, it shows above the comment it answers. And the score can't live in the path: every vote would rewrite a whole subtree's paths, 7 for one upvote on c2.

Cache each built page per sort, say for 5 seconds: a busy page is built once every 5 seconds instead of on every load.

Editing and deleting: a tombstone

When Ben deletes c3, four replies hang under it. Deleting the row would leave c4 to c7 under a comment that's gone, and deleting the subtree would erase other people's words. Instead keep the row and its path, blank its text and author, and show [deleted]. An edit changes only the text and marks it edited; the path never changes.

Moderation per site: the site id everywhere

All 50,000 sites share one system (a multi-tenant design), so every comment's key starts with its site id, and so does every role:

site · user      role
news.org · lia   moderator
blog.net · tom   moderator
news.org · ben   banned

Lia can remove c5 because her role and c5 both say news.org; she can't touch a comment on blog.net or ban Ben from other sites. She removes but never rewrites. Bans and rules live in each site's settings, checked when a comment is posted. Comments are split by site and page, so a page lives on one shard.

Same pattern as Reddit

Votes, scores, parent ids and cached built pages work as in Reddit. What changed is how a thread is found: by its path, not by walking parent ids.

Summary

  • Scope: nested replies on 50,000 sites, top or new, edits and deletes, moderators per site.
  • Data: a path per comment, like c1/c2/c3; the site id leads every key.
  • API: GET /comments?site=…&page=…, GET /comments/{id}/thread, POST /comments.
  • Numbers: about 25,000 loads and 250 new comments a second at busy hours.
  • Deep threads: 5 levels first, then one range scan by path prefix.
  • Sorting: siblings sorted in memory; built pages cached per sort.
  • Deleting: a [deleted] tombstone keeps the tree.
  • Moderation: the site id on every row and role.
Keep going with system designInteractive lessons from the building blocks to Twitter, Uber and payments, the way interviews ask. Free to start.
Start learning free