Low-level design
Part 1 of 3 · Comments and RepliesComments and Replies LLD - Spec, Design Steps & Concepts
Two-level in-memory comments/replies LLD: spec, structure choices, independent IDs, order, snapshots, concepts.
- 1Gist
- 2Maps
- 3Q&A
- 4Sandbox
Voice readout needs Web Speech Synthesis in this browser.
Question ladder
L1
What are the operations?
Answer
ensure_post, add_comment, add_reply, list_comments, list_replies, and get_thread.
L2
Why not a free-form tree?
Answer
The spec is two levels. A reply of a reply is a different product and a different invariant.
L3
Why a list and a dict?
Answer
The list preserves append order. The dict finds the parent comment in O(1) when a reply arrives.
L4
Why not sort by timestamp?
Answer
Clocks skew, and two inserts in the same millisecond swap. A monotonic created_seq under the lock does not.
L5
Why two id sequences?
Answer
comment_id and reply_id are different namespaces. A shared counter makes those APIs lie.
L6
What do callers get back?
Answer
Frozen dataclasses or a deep copy. Never the internal list.
L7
What does the concurrency test have to prove?
Answer
Unique ids, no lost appends, and list order matching creation order.
Failure modes
Reply to a missing comment
add_reply on an unknown id raises COMMENT_NOT_FOUND. It does not create a third level.
Leaked internal list
A caller appends to the returned replies and corrupts the store. Snapshots prevent that.
Timestamp reorder
Sorting by wall clock after the fact violates creation order.
Misconceptions
A nested dict is simpler so it is better.
It makes order and snapshots harder. The list-plus-index matches the API.
One global id is always cleaner.
Only if the prompt asked for one namespace. This prompt asks for independent sequences.
Low-level design means every pattern name.
It means types, invariants, and what the lock covers. The interfaces page is the vocabulary.
Interviewer traps
Add edit, delete, and notifications in v1.
v1 is create, list, and snapshot. Say what you are not building.
Reach for a database before the in-memory invariants are true.
SQLite can come later. The bug to kill first is order, isolation, and ids.
Design scenario
Same prompt for every reader.
Requirements
Independent ids, creation order, per-post isolation, validation, and snapshots under concurrency.
Traffic / scale
In-process. Threads share one service in the coding round.
Latency
Append and lookup are memory operations behind one lock.
Consistency
List order matches append order. Ids never repeat. A reply cannot outlive a missing parent.
Availability
Unknown post or comment is a typed error, not an empty success.
Failure assumptions
- Two threads add comments on the same post.
- A caller mutates whatever object you return.
Constraints
- Do not allow reply-to-reply.
- Do not order by wall clock.
Prompt
Design an in-memory comments service: posts have comments, comments have replies, and nothing is deeper.
API
Which errors are validation, and which are missing parent?
Data
What does the post map store, and what does the id index store?
Architecture
What does the lock cover, and which study owns mutex versus RWLock?
Structure for this spec
Prefer
List per post plus dict by comment id
Append order is the list. Reply attach is the dict. One lock updates both.
- Creation order does not depend on the clock.
- Parent lookup is O(1).
- Two levels fall out of the types.
Alternative
An unlimited tree or a timestamp sort
Flexible, and wrong for the prompt.
- Reply-to-reply sneaks in.
- Clock skew reorders the page.
- Snapshots get harder.
Overview
Machine-coding LLD: an in-memory Comments and Replies service with exactly two levels (post -> comment -> reply). Validate input and relationships, keep independent auto-increment IDs for comments and replies under concurrency, preserve creation order regardless of timestamps, isolate state per post, and return defensive snapshots so callers cannot mutate internals. Choose data structures deliberately (ordered lists + id index), not a generic nested tree.
Spec summary
| Operation | Rules |
|---|---|
| ensure_post | Idempotent register of post container |
| add_comment | Validate author/body; post must exist; allocate comment id; append in order |
| add_reply | Comment must exist; allocate reply id independently; append under parent |
| list_comments / list_replies | Creation order; defensive copies |
| get_thread | Nested snapshot via deepcopy |
| Errors | INVALID_*, POST_NOT_FOUND, COMMENT_NOT_FOUND |
Step-by-step design
- Clarify: two levels only, no edit/delete in v1, in-memory OK, concurrency required.
- Choose structures: ordered list per post + dict by comment id for O(1) reply attach.
- Two counters:
comment_seq,reply_seq, plus optionalorder_seqfor global creation stamps. - Validate author/body lengths and non-empty strip.
- Guard all mutations with one RLock (or per-post locks if you justify cross-post reply impossibility).
- Return
CommentView/ReplyViewfrozen dataclasses or deep-copied dicts. - Tests: order, isolation, validation, independent IDs, concurrent unique IDs, snapshot immutability.
Post, comment, reply, snapshot
Diagram 1. Ids are allocated under the lock. A missing parent is a failure, not a new node.
- 1
ensure_post
Register an empty ordered list for the post. A second call is a no-op. - 2
add_comment
Validate author and body. Allocate comment_seq. Append to the post list and the id index. - 3
add_reply
Look up the parent comment. Missing parents stop here. - 4
COMMENT_NOT_FOUND
Do not invent a node and do not attach a reply to a reply. - 5
Allocate reply_seq
The reply counter is independent of the comment counter. - 6
Return snapshots
list and get_thread copy values out. Callers cannot mutate the store through the result.
Decisions
- 1
1. ensure_post post_id
- next2. add_comment under post
- 2
2. add_comment under post
- next3. Allocate comment_seq under lock
- 3
3. Allocate comment_seq under lock
- next4. Append to post list and id index
- 4
4. Append to post list and id index
- next5. add_reply comment_id
- 5
5. add_reply comment_id
- next6. Parent comment exists?
- ?
6. Parent comment exists?
- No7. Failure: COMMENT_NOT_FOUND
- Yes8. Allocate reply_seq under lock
- 7
7. Failure: COMMENT_NOT_FOUND
- 8
8. Allocate reply_seq under lock
- next9. Append to parent.replies
- 9
9. Append to parent.replies
- next10. list or get_thread returns snapshots
- 10
10. list or get_thread returns snapshots
Lesson map
Comments and Replies LLD - Spec, Design Steps & Concepts
Two-level in-memory comments/replies LLD: spec, structure choices, independent IDs, order, snapshots, concepts.
Architecture. Architecture
Select a node to see why it exists, or an edge to see the protocol, direction, effect, and consequence.
Mermaid export
flowchart TB a["1. ensure_post post_id"] b["2. add_comment under post"] c["3. Allocate comment_seq under lock"] d["4. Append to post list and id index"] e["5. add_reply comment_id"] f["6. Parent comment exists?"] g["7. Failure: COMMENT_NOT_FOUND"] h["8. Allocate reply_seq under lock"] i["9. Append to parent.replies"] j["10. list or get_thread returns snapshots"] a -->|continues| b b -->|continues| c c -->|continues| d d -->|continues| e e -->|continues| f f -->|No| g f -->|Yes| h h -->|continues| i i -->|continues| j
Flow
- 1
Need O1 reply attach?
- YesDict comment_id to Comment
- 2
Dict comment_id to Comment
- nextMaintain both indexes under one lock
- 3
Need stable order?
- YesList per post for comments
- 4
List per post for comments
- nextMaintain both indexes under one lock
- 5
Maintain both indexes under one lock
Concepts used, learn more
Read the underlying idea on its own study page. This lesson applies it. It does not replace those pages.
- Low-Level Design Under Time — Interfaces, State & Tradeoffs
- Mutexes, Condition Variables, Deadlocks & Happens-Before
- Mutex vs RWLock
- Atomics vs Locks
- Atomics, Memory Ordering & Lock-Free — Races, Barriers & When Locks Win
| Concept | Role | Study page |
|---|---|---|
| LLD interfaces and invariants | Service boundary | Interfaces and state |
| Mutex / RLock | Concurrent ID allocation | Mutexes |
| Mutex vs RWLock | Read-heavy list optimization later | Mutex vs RWLock |
Comparative: data structures
| Structure | Pros | Cons | Verdict here |
|---|---|---|---|
| List per post + dict by id | Order + O(1) parent lookup | Extra index to maintain | Chosen |
| Only nested dict of dicts | Simple mentally | Weak order; harder snapshots | Weak |
| Tree with parent pointers unlimited depth | Flexible | Spec forbids >2 levels; overbuilt | Reject |
| Store sorted by timestamp | Feels natural | Clock skew reorders | Reject; use seq |
Interview Q&A
Why not trust timestamps for order?
Answer
NTP skew and same-millisecond inserts reorder. A monotonic created_seq under the lock is stable.
Why separate comment and reply IDs?
Answer
Product IDs often live in different namespaces; independent sequences match APIs that return comment_id vs reply_id without collisions across types.
Can replies reference another reply?
Answer
Not in this spec. Reject in validation if you add a parent_kind later.
What does ensure_post do the second time?
Answer
It is idempotent. The post container already exists, so the call leaves the comment list in place.
Which concept page is the design vocabulary?
Answer
Low-level design under time is interfaces, invariants, and state. Mutexes is the lock. Mutex versus RWLock is the later read-heavy optimization. Atomics is what you would reach for if you tried to drop the lock.
Can a reply point at another reply?
Answer
Not in this spec. add_reply takes a comment id. A parent_kind would be a new product decision, not a silent extra level.
Pitfalls
- Returning internal lists by reference.
- Single global ID space when the prompt asks for independent sequences.
- Allowing reply-to-reply accidentally.
Series map
- This hub - spec and design.
- Full in-memory solution and tests.
- Concurrent IDs, snapshots and post isolation deep dive.
Go Deeper
Related
The series pager also walks these pages.