Social media post
Low-level design of a social-media post system -- creating posts with text/images/video, reactions (like/love/haha), threaded comments, feed generation, visibility controls, and content moderation hooks.
The Problem
Your social platform has 50 million monthly active users, and the post creation path is a single 800-line PostController class. Every feature request (reactions beyond "like," threaded comments, visibility controls) requires surgery on that monolith. Last quarter a developer added video uploads by copy-pasting the image upload block and changing three lines. The result: video thumbnails render as broken images, and comment threads deeper than two levels crash the mobile app because the flat-list query has no depth awareness.
A social media post system sits at the center of every social platform. Users create posts with text and media attachments, other users react and comment, and the platform generates a personalized feed. The tricky parts are threaded comment trees, a reaction model that allows one reaction per user per post, feed generation strategies that balance freshness and engagement, and content moderation that filters harmful content without blocking legitimate posts.
Design the core classes for a social media post system that supports post creation with media, reactions (like/love/haha/wow/sad/angry), threaded comments with depth limits, pluggable feed generation strategies, visibility controls (public/friends/private), and content moderation hooks.
Requirements
Clarifying Questions
Before jumping into class design, ask questions to turn the vague prompt into a concrete specification. Cover four areas: core actions, error handling, boundaries, and future extensions.
You: "What types of media can a post contain? Just images, or also video and GIFs?"
Interviewer: "Images, video, and GIFs. Each post can have up to 10 media attachments. Assume media is already uploaded; you receive a media ID."
Good. We are not building the upload pipeline. We receive media IDs and attach them to the post. That keeps our scope tight.
You: "Are comments flat or threaded? If threaded, is there a depth limit?"
Interviewer: "Threaded. Cap depth at 3 levels. A reply to a depth-3 comment attaches at depth 3 instead of going deeper."
Three levels of nesting. That means a comment can have child comments, and each child can have children, but we clamp at depth 3. This screams Composite pattern with a guard.
You: "Can users react to comments too, or only to posts?"
Interviewer: "Both. Same reaction types: like, love, haha, wow, sad, angry. One reaction per user per target. Changing your reaction replaces the old one."
Reactions on both posts and comments with the same interface. We will need a generic "reactable" target rather than duplicating logic.
You: "Who decides what appears in a user's feed? Is it purely chronological?"
Interviewer: "Support multiple strategies: chronological, engagement-weighted (likes + comments), and friends-first. The platform picks the strategy at runtime."
Multiple interchangeable algorithms, same input and output. That is the Strategy pattern for feed generation.
You: "How should content moderation work? Real-time blocking, or queue-based review?"
Interviewer: "Pluggable. Start with a keyword filter that blocks known bad words. Design it so we can swap in ML-based filtering or a manual review queue later."
Strategy pattern again. A ContentFilter interface with multiple implementations. The post creation pipeline calls the active filter before publishing.
You: "What visibility levels exist, and do they affect comments and reactions?"
Interviewer: "Three levels: public, friends-only, and private. Visibility controls who can see the post in their feed. Anyone who can see the post can comment and react on it."
Visibility is a post-level attribute that gates access. We do not need per-comment visibility.
You: "Should the system send notifications when someone comments on your post or reacts to it?"
Interviewer: "Yes. Notify the post author on new comments and reactions. Notify a commenter when someone replies to their comment. Also notify mentioned users."
Observer pattern. Post events (new comment, new reaction, mention) trigger notifications to the relevant users.
You: "Can users edit or delete their posts after publishing?"
Interviewer: "Yes. Editing marks the post as edited with a timestamp. Deleting soft-deletes it."
Soft delete with an editedAt timestamp. No revision history for now.
Final Requirements
Functional Requirements:
- Create a post with text content, up to 10 media attachments, and a visibility level.
- Support six reaction types (like/love/haha/wow/sad/angry) on posts and comments. One reaction per user per target; changing replaces the old one.
- Add threaded comments with a maximum depth of 3. Replies beyond depth 3 clamp to depth 3.
- Generate a personalized feed using pluggable strategies: chronological, engagement-weighted, and friends-first.
- Run content moderation on post creation with pluggable filters (keyword, ML-based, manual review queue).
- Notify users on new comments, reactions, replies, and mentions.
- Support edit (with timestamp) and soft-delete for posts.
Non-Functional Requirements:
- Thread safety for concurrent reactions and comments on the same post.
- Extensibility for new reaction types, feed strategies, and moderation filters without modifying existing code.
- Lazy loading for comment trees (do not fetch the entire tree upfront).
Out of Scope:
- Media upload/transcoding pipeline
- User authentication and authorization
- Persistence layer and database schema
- Real-time push (WebSocket) delivery
- Hashtag trending and search
Example Inputs and Outputs
Scenario 1: Create a post with media
- Input: User "alice" creates a post with text "Beach day!" and two image media IDs, visibility = FRIENDS.
- Expected: A
Postobject is created with status PUBLISHED, twoMediaattachments linked, and the feed distributor pushes the post to Alice's friends' feeds. The keyword filter runs and passes. - Why: Validates the happy-path creation flow and feed distribution.
Scenario 2: Threaded comment with depth clamping
- Input: User "bob" comments on Alice's post (depth 0). User "carol" replies to Bob's comment (depth 1). User "dave" replies to Carol (depth 2). User "eve" replies to Dave (depth 3, the cap).
- Expected: Eve's comment is stored at depth 3 with
parentCommentpointing to Dave's comment. The system does not create depth 4. Eve's reply renders under Dave's comment. - Why: Validates the depth-clamping rule from the requirements.
Scenario 3: Reaction toggle and replacement
- Input: User "frank" reacts LIKE to Alice's post. Frank then reacts LOVE to the same post.
- Expected: The LIKE is removed (count decrements), and LOVE is added (count increments). Frank has exactly one reaction on the post: LOVE.
- Why: Validates the one-reaction-per-user-per-target rule and the replacement behavior.
Try it yourself
Before reading the solution, spend 15-20 minutes sketching your class diagram. Focus on three things: how you model threaded comments (tree vs flat list), how you make feed generation swappable, and where moderation fits in the post creation flow. Compare your approach with the walkthrough below.
Step 1: Identify Core Entities
Start by scanning your requirements for nouns. "Posts," "media," "reactions," "comments," "feed," "visibility," and "content filter" jump out immediately. Each one has a distinct lifecycle and responsibility.
| Entity | Responsibility | Key attributes |
|---|---|---|
| Post | The central content unit. Holds text, media references, visibility, and aggregate reaction/comment counts. | id, authorId, content, visibility, status, reactionCounts, createdAt |
| Media | A single attachment (image, video, GIF) linked to a post. | id, type, url, postId |
| Comment | A node in the threaded comment tree. Knows its parent and children. | id, authorId, content, depth, parentComment, children |
| Reaction | A user's emotional response to a post or comment. One per user per target. | userId, targetId, targetType, reactionType |
| User | The actor. Owns posts, writes comments, reacts. | id, name, friendIds |
| Feed | A personalized list of posts for a user, built by a pluggable strategy. | userId, posts |
| ContentFilter | Checks post content against moderation rules before publishing. | (interface with multiple implementations) |
| PostEventListener | Receives notifications when post-related events occur (comment added, reaction added). | (interface) |
Notice that Comment is the most interesting entity here. A flat comment list would be easy, but threaded comments form a tree. Each Comment can hold child comments, which is the Composite pattern. We separate Reaction from both Post and Comment because it has its own lifecycle (create, replace, remove) and needs a unique constraint per user per target. Merging reactions into Post would violate SRP.
Step 2: Define Relationships and Class Design
Now that we know what exists, we map how these entities connect. The class diagram reveals the structure before we write a single line of logic.
Post
The orchestrator entity. It owns the content, media list, and aggregate counts. I deliberately keep reaction counts as a Map<ReactionType, Integer> on Post itself rather than computing them from the Reaction table every time. That is a read-optimization trade-off: one atomic increment on write saves a COUNT query on every read.
Deriving state from requirements:
| Requirement | What Post must track |
|---|---|
| "Create a post with text, media, visibility" | content, mediaList, visibility |
| "Support edit with timestamp" | editedAt |
| "Soft-delete" | status (PUBLISHED / DELETED) |
| "Six reaction types with counts" | reactionCounts map |
| "Comment count" | commentCount |
Deriving methods from needs:
| Need | Method |
|---|---|
| Attach media during creation | addMedia(Media) |
| Reaction added or changed | incrementReaction(type), decrementReaction(type) |
| Edit post content | edit(newContent) |
| Delete post | softDelete() |
Comment (Composite node)
Each Comment knows its depth and holds a list of child Comments. The addReply method enforces the depth-3 cap. This is the Composite pattern: a Comment is both a leaf (if it has no children) and a composite (if it has replies). The tree structure means we can render nested threads naturally by walking children recursively.
Deriving state from requirements:
| Requirement | What Comment must track |
|---|---|
| "Threaded with depth limit 3" | depth, parentComment, children |
| "Reactions on comments" | reactionCounts map |
| "Reply notification" | authorId, postId |
Reaction (value object)
Reaction is a thin record. Its identity is the combination of (userId, targetId, targetType). We never have two Reactions for the same user on the same target.
After deriving each class, the key relationship decisions are:
- Post owns Media (composition) because media does not exist without a post. Deleting a post removes its media.
- Comment references Post (association) because we need to navigate from comment to post for notifications, but comments can be lazily loaded.
- FeedStrategy, ContentFilter, PostEventListener are interfaces because they define interchangeable behaviors. New strategies and filters plug in without touching existing code.
Continue Reading with Premium
Unlock this article and every other in-depth system design guide on the platform with NotesFromSDE Premium.