Hey folks, Rahul here ๐
Comments seem trivial until you need nesting. Reddit, Hacker News, GitHub โ they all solve the same recursive data problem differently. And the frontend challenges go way beyond rendering a tree: think optimistic inserts, collapse state, real-time streaming, pagination within threads, and sort order that doesn't break when new comments arrive mid-scroll.
This is one of those problems where a naive recursive approach works for 50 comments but melts the browser at 5,000. Let's build it right.
R โ Requirements
Functional Requirements
- Display comments in a threaded/nested tree structure
- Users can reply to any comment (creating child nodes)
- Collapse/expand individual threads
- Sort comments by: newest, oldest, most liked, controversial
- Like/dislike individual comments
- Edit and delete own comments
- Load more replies (pagination within threads)
- "Jump to parent" for deeply nested contexts
Non-Functional Requirements
- Performance: Render 5,000+ comments without jank
- Optimistic UI: New comments appear instantly before server confirmation
- Real-time: New comments from other users stream in without refresh
- Accessibility: Proper tree role semantics and focus management
- Deep linking: Direct URL to any comment with context
A โ Architecture
Data Structure: Flat vs. Nested
The core architectural decision is how you store and render the tree. There are two approaches:
Option A: Recursive Nested Objects โ
interface NestedComment {
id: string;
content: string;
children: NestedComment[]; // Recursive
}
// Problem: Updating a deeply nested comment requires deep cloning
// Problem: React can't efficiently diff deep trees
// Problem: Normalization is painfulOption B: Flat Map + Parent References โ
interface Comment {
id: string;
parentId: string | null;
content: string;
authorId: string;
depth: number;
childCount: number;
createdAt: string;
}
// Store as a flat normalized map
interface CommentsState {
byId: Record<string, Comment>;
rootIds: string[]; // Top-level comment IDs in sort order
childrenMap: Record<string, string[]>; // parentId โ sorted child IDs
collapsedIds: Set<string>; // Which threads are collapsed
}Why flat? Updating a single comment is O(1) โ just update byId[id]. Adding a child is O(1) โ push to childrenMap[parentId]. React diffs are shallow. This is the Reddit/HN pattern.
Component Tree
Rendering Strategy: Flattened Virtual List
Here's the key insight: don't render the tree recursively with React components. Instead, flatten the visible tree into a list and render it linearly with indentation:
This approach means you can plug in a virtual list (like react-window) directly โ it's just a flat array with varying indentation. At 5,000 comments, only ~20 are in the viewport.
D โ Data Model
Server Response
Optimistic Comment State
I โ Interface Definition
API Endpoints
Real-Time Events
O โ Optimizations
1. Depth Capping with "Continue Thread"
2. Collapse State Persistence
3. Deep Link with Context Loading
4. Virtualized Comments for Large Threads
5. Markdown Rendering with Sanitization
Production Gotchas Rahul Has Debugged ๐ฅ
- Sort + Real-time Conflict: If sorted by "most liked" and a new comment arrives via WebSocket, don't inject it at the top โ it has 0 likes. Show a "New comments available" banner instead.
- Delete Semantics: Never hard-delete comments with children. Show "[deleted]" placeholder to preserve thread structure. Only hard-delete leaf comments.
- Reply Form Focus: When "Reply" is clicked, the inline composer appears and steals focus. On mobile, this triggers the keyboard, which scrolls the page. Use
scrollIntoView({ block: 'nearest' })to minimize disruption. - Recursive useEffect: Don't fetch children inside a recursive component's useEffect โ you'll fire N requests for N visible threads simultaneously. Batch: fetch the first 2 levels of comments in a single API call.
- XSS via Markdown: Always sanitize user-generated HTML. Markdown libraries like
markedcan produce<img onerror="alert(1)">from crafted input. DOMPurify is non-negotiable.
Next up: #9: Design a Collaborative Text Editor โ CRDTs vs OT, cursor presence, conflict resolution, and the operational complexity behind Google Docs. โ๏ธ