Build a Comments Thread: Nested Replies, Pagination, Optimistic Updates

“Build a comments thread” is one of the most realistic frontend machine-coding problems — every product team builds one eventually. The interview probes data shape, recursion, pagination, and the optimistic-update patterns that separate junior from senior frontend engineers. This guide covers the canonical interview structure.

Clarify scope

  • Flat or nested? If nested, how deep? (Reddit allows arbitrary, Hacker News too; Disqus caps; Twitter does single reply)
  • Sort order: chronological, by upvotes, by recency-of-last-reply?
  • Pagination of top-level vs replies?
  • Edit, delete, react, mention — all in scope?
  • Real-time updates (new comments while reading)?

Data shape

Nested comments are usually delivered as a flat list with parent IDs:

{
  id: "c1",
  parentId: null,
  body: "...",
  author: {...},
  createdAt: "...",
  replyCount: 5,
  cursor: "..."
}

The client builds a tree from the flat list. Avoid receiving nested JSON from the server — pagination and partial loading get hard.

Pagination strategies

  • Top-level page: cursor-based pagination of root comments
  • Reply expansion: “Show 5 more replies” loads paged replies for one parent
  • Deep nesting: “Continue this thread” link loads a sub-tree on a separate route (Reddit pattern)

Building the tree on the client

function buildTree(comments) {
  const map = new Map(comments.map(c => [c.id, { ...c, children: [] }]));
  const roots = [];
  for (const c of map.values()) {
    if (c.parentId) map.get(c.parentId)?.children.push(c);
    else roots.push(c);
  }
  return roots;
}

Render each level recursively. Limit recursion depth in JSX with a “depth” prop — past a threshold, render a “Continue” link instead of nesting further.

Optimistic updates — the senior signal

When the user posts a reply:

  1. Generate a temp client-side ID
  2. Insert the comment immediately into the local tree
  3. Mark it visually (slightly faded, “Posting…” indicator)
  4. Send the request
  5. On success: replace temp ID with server ID; mark as confirmed
  6. On failure: keep the comment but show a “Retry / Discard” affordance; do not silently delete

Junior candidates skip the failure branch. That is the test.

Edit and delete

  • Edit: optimistic update with old-content kept until server confirms; on failure, revert with a clear toast
  • Delete: tombstone pattern — keep the node, replace body with “[deleted]” so children remain visible. Most platforms (Reddit, HN) do this.

Real-time updates

If new comments can arrive while reading:

  • WebSocket subscription to the thread
  • Buffer arrivals; show a “5 new replies — click to load” banner instead of inserting silently
  • Inserting silently is hostile — the user loses scroll position

Performance

  • Virtualize long top-level lists (react-virtual, TanStack Virtual)
  • Memoize comment components by ID — avoid re-rendering the whole tree on a single edit
  • Defer rendering of deeply nested comments below the fold (Intersection Observer)
  • Cap initial render depth; expand on user action

Accessibility

  • Each comment as a <article>
  • Reply form is a labeled <form> nested below the parent comment
  • “Show N replies” is a <button> with aria-expanded
  • Focus management: when a reply form opens, move focus to the textarea; when closed, return to the trigger button

Spam and moderation hooks

  • “Report” action on each comment
  • Admin/mod controls behind feature flag
  • “Collapsed by community” state for downvoted comments (HN/Reddit pattern)

Edge cases interviewers love

  • User edits a comment while a child reply is being typed — no data loss
  • Network drops mid-post — clear retry UX
  • User deletes a comment with active replies — tombstone, do not orphan children
  • Reply count out of sync after pagination — recalculate on insertion

Frequently Asked Questions

Should I use a library?

For interview, no. For production, react-comments-section and similar exist; most teams build their own because requirements diverge.

How do I handle 10,000 comments?

Lazy-load top-level comments via cursor pagination; never render all at once. Virtualize the visible window.

What about Markdown rendering?

Use a battle-tested library (markdown-it, react-markdown). Sanitize aggressively. Disable raw HTML; allow only the markdown subset you intend.

Scroll to Top