“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:
- Generate a temp client-side ID
- Insert the comment immediately into the local tree
- Mark it visually (slightly faded, “Posting…” indicator)
- Send the request
- On success: replace temp ID with server ID; mark as confirmed
- 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>witharia-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.