# Low Level Design: Threaded Discussion System

Source: https://www.techinterview.org/post/3233470864/lld-threaded-discussions/
Updated: 2026-04-17 · techinterview.org

## Data Model

Closure table for efficient subtree queries:


```
ThreadAncestor (
  ancestor_id   BIGINT,
  descendant_id BIGINT,
  depth         INT,
  PRIMARY KEY (ancestor_id, descendant_id)
)

Post (
  id         BIGINT PK,
  thread_id  BIGINT,
  parent_id  BIGINT NULL,
  user_id    BIGINT,
  body       TEXT,
  score      INT DEFAULT 0,
  created_at TIMESTAMP,
  deleted    BOOL DEFAULT FALSE
)
```

Each post inserts a self-referencing row (depth=0) plus one row per ancestor in `ThreadAncestor`. Subtree fetches are a single join — no recursive CTEs needed.

## Voting


```
Vote (
  user_id BIGINT,
  post_id BIGINT,
  value   SMALLINT CHECK (value IN (1,-1)),
  PRIMARY KEY (user_id, post_id)
)
```

Score update is atomic:


```
UPDATE post SET score = score + :delta WHERE id = :post_id;
```

On vote change: subtract old value, add new value in a single UPDATE.

## Sort Algorithms

  - **New** — ORDER BY created_at DESC

  - **Top** — ORDER BY score DESC

  - **Hot** — Wilson score lower confidence bound (accounts for vote count); precomputed periodically and stored as `hot_score FLOAT` on post row

## Loading a Thread

Fetch all posts in a thread via closure table join, then build the tree client-side or server-side from the flat list using parent_id references. For large threads, limit initial fetch to depth <= N.


```
SELECT p.* FROM post p
JOIN thread_ancestor ta ON ta.descendant_id = p.id
WHERE ta.ancestor_id = :root_post_id AND ta.depth <= 3
ORDER BY p.score DESC;
```


## Lazy Loading

Load top 3 replies per parent on initial render. "Load more replies" button triggers a paginated fetch for the next page of children for that specific parent_id.

## Collapse / Expand

Track `expandedPostIds` in client state (Set). Toggle on click. Collapsed nodes skip rendering their subtree. No server round-trip needed for collapse.

## Thread Lock

`forum_thread.locked = TRUE` — checked on write path. Locked threads return 403 on new post attempts. Display lock badge in UI.

## Thread Subscription

Subscribe table: `Subscription(user_id, thread_id)`. On post approval, notify subscribed users whose posts are direct parents of the new reply. Notifications sent via async job queue (email or in-app).

## Moderator Tools

Remove subtree: use closure table to find all descendants, then set `deleted=TRUE` on all. Soft delete preserves structure; body replaced with `[removed]` on read.


```
UPDATE post SET deleted=TRUE
WHERE id IN (
  SELECT descendant_id FROM thread_ancestor WHERE ancestor_id=:target_id
);
```


See also: [Netflix Interview Guide 2026: Streaming Architecture, Recommendation Systems, and Engineering Excellence](https://www.techinterview.org/post/3233460274/netflix-interview-guide-2026-streaming-architecture-recommendation-systems-and-engineering-excellence/)

See also: [Atlassian Interview Guide](https://www.techinterview.org/post/3233459919/atlassian-interview/)

See also: [Meta Interview Guide 2026: Facebook, Instagram, WhatsApp Engineering](https://www.techinterview.org/post/3233460272/meta-interview-guide-2026-facebook-instagram-whatsapp-engineering/)
