How Do You Design a Comment System?
Learn how to design a comment system: threading, cursor pagination, denormalized counters, and async moderation.
Expected Interview Answer
A comment system stores comments as nested or flat records keyed by the parent content ID, uses a database that supports efficient tree or adjacency-list queries (or a materialized-path/nested-set model for deep threading), and layers caching plus pagination on top so a post with thousands of comments still loads quickly and stays consistent under concurrent writes.
Each comment is stored with a reference to its parent content (post/video ID) and, for threaded replies, a reference to its parent comment ID, forming an adjacency list; for very deep or heavily-read threads, a materialized path or closure table can make fetching an entire subtree a single query instead of a recursive one. Comments are paginated (cursor-based, not offset, so new comments don’t shift pages) and sorted by recency, top-voted, or relevance depending on the product. A write path handles comment creation with basic validation, spam/abuse filtering (often async via a moderation queue), and increments denormalized counters (comment count, like count) rather than counting rows live. Read-heavy hot threads are cached (per-page or per-subtree) and invalidated on new writes, and real-time delivery of new comments to active viewers is typically handled via a separate pub/sub or WebSocket layer so it doesn’t block the core write path.
- Adjacency-list or materialized-path storage supports efficient threaded replies at any depth
- Cursor-based pagination keeps comment feeds stable even as new comments are added
- Denormalized counters avoid expensive live counts on high-traffic content
- Separating moderation and real-time delivery from the write path keeps comment posting fast
AI Mentor Explanation
A comment system is like the stands at a match where fans jot notes on a shared board, each note pinned under the specific incident it reacts to, and replies pinned under earlier notes to form a thread. A steward periodically counts and displays the total note count on a summary sign instead of recounting live every time someone looks. New notes appear at the top for anyone actively watching, but people scrolling through older notes see a stable list that does not jump around. That structured, threaded, counted, and stably paginated layout is exactly how a comment system organizes discussion.
Step-by-Step Explanation
Step 1
Store comments with parent references
Each comment stores the parent content ID and, for replies, the parent comment ID, forming an adjacency list (or materialized path for deep threads).
Step 2
Write with async moderation
On creation, validate and enqueue the comment for async spam/abuse filtering, then increment a denormalized comment counter.
Step 3
Paginate with a cursor
Fetch comments using a cursor keyed on timestamp or vote score, not offset, so newly inserted comments never shift existing pages.
Step 4
Cache and push updates
Cache hot comment threads and invalidate on write; push new comments to active viewers over a separate pub/sub or WebSocket channel.
What Interviewer Expects
- Describes a threading model (adjacency list, materialized path, or closure table) and its trade-offs
- Uses cursor-based pagination and explains why offset pagination breaks under concurrent writes
- Mentions denormalized counters instead of live counting for comment/like counts
- Separates moderation and real-time delivery from the synchronous write path
Common Mistakes
- Using offset-based pagination that shifts as new comments arrive
- Recursively querying the whole tree on every read instead of a flatter storage model
- Counting comments live with COUNT(*) on every page load instead of denormalizing
- Doing synchronous spam/moderation checks that block comment submission latency
Best Answer (HR Friendly)
“A comment system stores each comment with a link to what it is replying to, so replies naturally form threads. To keep things fast, we cache popular threads, use a counter instead of recounting comments every time, and page through comments in a way that does not jump around when new ones come in. Spam checking and live delivery of new comments happen separately so posting a comment always feels instant.”
Code Example
// Comment table: id, contentId, parentCommentId, authorId, body, createdAt
async function postComment({ contentId, parentCommentId, authorId, body }) {
const comment = await db.comments.insert({ contentId, parentCommentId, authorId, body, createdAt: Date.now() })
await moderationQueue.enqueue(comment.id) // async spam/abuse check
await db.contents.increment(contentId, "commentCount", 1) // denormalized
return comment
}
async function getComments(contentId, cursor, limit = 20) {
// cursor-based: fetch comments older than the last seen createdAt
return db.comments.find({
contentId,
parentCommentId: null,
createdAt: cursor ? { $lt: cursor } : undefined,
}).sort({ createdAt: -1 }).limit(limit)
}Follow-up Questions
- How would you support sorting comments by “top” (most liked) instead of recency?
- How would you fetch a deeply nested reply thread efficiently without N+1 queries?
- How do you keep the denormalized comment counter consistent if the moderation queue later deletes spam?
- How would you push newly posted comments to viewers currently on the page in real time?
MCQ Practice
1. Why is cursor-based pagination preferred over offset-based pagination for comments?
Offset pagination recalculates position by row count, which shifts under concurrent inserts; cursor pagination anchors to a stable value like timestamp.
2. Why does a comment system typically use a denormalized comment counter instead of COUNT(*)?
Recomputing counts live does not scale for popular content; incrementing a stored counter on write is far cheaper to read.
3. Why is spam/abuse moderation often done asynchronously after a comment is posted?
Async moderation via a queue keeps comment submission latency low while still filtering abuse shortly after posting.
Flash Cards
How are threaded comments typically stored? — As an adjacency list (parent comment ID) or, for deep threads, a materialized path/closure table.
Why cursor-based pagination for comments? — It keeps pages stable when new comments are inserted, unlike offset pagination which shifts.
Why denormalize the comment count? — To avoid an expensive live COUNT(*) on every page load of high-traffic content.
Why handle moderation asynchronously? — So spam/abuse checks do not block or slow down the comment submission path.