Design LinkedIn
OOP design for a professional networking platform covering user profiles, connections, job postings, feed generation, endorsements, and messaging.
The Problem
Your company runs a professional networking platform with 30 million users. The profile service is a 3,000-line ProfileController that handles connections, job postings, feed generation, and messaging all in one class. Last month a developer added "endorsements" by jamming a Map<String, Integer> into the User table. Now endorsement counts are wrong because there is no deduplication, the feed shows stale posts because it queries every user's posts in a loop, and connection requests silently disappear when two users send requests to each other simultaneously.
LinkedIn-style systems are among the hardest LLD problems because they combine multiple social features into one cohesive platform. Users maintain rich profiles with education, experience, and skills. They form connections through a request/accept lifecycle. They publish posts and interact through comments and reactions. They search for and apply to jobs. They message each other in real-time conversations. Each of these features alone is straightforward, but the challenge is designing clean boundaries between them while keeping them integrated through notifications and feed generation.
Design the core classes for a professional networking platform that handles user profiles with structured sections, connection requests with state management, posts with comments and reactions, job postings with applications, messaging between connected users, skill endorsements, and a personalized feed.
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 sections does a user profile contain? Is it fixed fields or extensible sections?"
Interviewer: "Profiles have three section types: education entries, experience entries, and skills. Each user can have multiple entries of each type. Design it so adding new section types later is easy."
Multiple section types with the same lifecycle (add, remove, display) screams Composite pattern. Each section type implements a common ProfileSection interface.
You: "How do connections work? Is it a mutual follow, or a request/accept model?"
Interviewer: "Request/accept model. User A sends a request to User B. User B can accept, reject, or ignore it. Either party can withdraw a pending request. Once accepted, the connection is mutual."
Classic state machine: PENDING to ACCEPTED, REJECTED, or WITHDRAWN. We need to prevent duplicates and handle the race where both users send requests simultaneously.
You: "Do we need to track connection degree? Like 1st, 2nd, and 3rd degree connections?"
Interviewer: "Yes. 1st degree means directly connected. 2nd degree means you share a mutual connection. 3rd degree is two hops away. This affects feed visibility and messaging permissions."
Connection degree is a graph traversal problem. We store direct connections and compute 2nd/3rd degree on demand.
You: "How does the feed work? Purely chronological, or does it rank posts?"
Interviewer: "Support multiple ranking strategies: chronological, engagement-weighted based on reactions and comments, and connection-proximity which prioritizes posts from 1st-degree connections. The platform picks the strategy at runtime."
Three interchangeable algorithms with the same input and output. Classic Strategy pattern for feed ranking.
You: "What about job postings? Who creates them and how do users apply?"
Interviewer: "Companies post jobs with a title, description, required skills, and experience level. Users apply by submitting their profile. Support job matching that recommends jobs based on skills, experience, and location."
Job matching is another Strategy pattern. Skill-based, experience-level, and location-based matching are separate algorithms we can swap at runtime.
You: "How do endorsements work? Is it different from recommendations?"
Interviewer: "Endorsements are simple: a user endorses a specific skill on another user's profile. One endorsement per user per skill. No free-text recommendations."
Simple and deduplicated. Each endorsement links endorser, endorsed user, and skill. We reject duplicates.
You: "Should the system send notifications? What events trigger them?"
Interviewer: "Yes. Notify on: connection request received, connection accepted, endorsement received, job match found, comment on your post, and new message. Design it so adding new notification types is easy."
Observer pattern. Multiple event types, multiple listener types. A NotificationService observes platform events and dispatches notifications.
You: "Do we need messaging between users? Real-time or just stored messages?"
Interviewer: "Support conversations between connected users. A conversation holds a sequence of messages. Store and deliver them. Real-time push is out of scope."
Conversations between connected users, messages ordered by timestamp. The connection check gates who can message whom.
Perfect. You have clarified scope. The core system handles structured profiles, connection lifecycle, posts with engagement, job postings with matching, endorsements, messaging, feed generation, and notifications.
Final Requirements
Functional Requirements:
- Users create and manage profiles with education, experience, and skills sections
- Users send, accept, reject, and withdraw connection requests with duplicate prevention
- Users publish posts and interact through comments and reactions (like, celebrate, insightful, love, curious)
- Companies post jobs with required skills and experience level; users apply to jobs
- Users endorse specific skills on another user's profile, one endorsement per user per skill
- Connected users exchange messages within conversations
- The feed generates a personalized post list using pluggable ranking strategies
- Job matching recommends relevant jobs to users based on configurable criteria
Non-Functional Requirements:
- Thread safety for concurrent connection requests and endorsements
- Extensibility for new profile section types, feed strategies, and job matching algorithms
- Efficient connection degree computation (no full graph traversal per query)
Out of Scope:
- UI rendering and REST API layer
- Database persistence and ORM mapping
- Real-time push notifications and WebSocket delivery
- Company pages and admin dashboards
- Premium features (InMail, profile viewers)
- Content moderation and spam filtering
Real-World Examples
Scenario 1: Connection request flow
- Alice sends a connection request to Bob
- Bob receives a notification and accepts the request
- Both Alice and Bob appear in each other's 1st-degree connections
- Alice can now message Bob directly
Scenario 2: Post and feed interaction
- Charlie publishes a post about a new project
- Alice (1st-degree connection) sees it in her feed ranked by connection proximity
- Alice reacts with "celebrate" and leaves a comment
- Charlie receives notifications for both the reaction and the comment
Scenario 3: Job application flow
- Acme Corp posts a "Senior Java Engineer" job requiring Java, Spring Boot, and 5+ years experience
- The job matching system recommends the posting to Dave, whose profile lists Java (10 endorsements), Spring Boot (5 endorsements), and 7 years at his current company
- Dave applies using his profile
- The system records the application with a SUBMITTED status
Try It Yourself
Try it yourself
Before reading the solution, spend 20 minutes sketching your class diagram. Start with the entity relationships: how does a User relate to a Profile, Connection, Post, and Job? Focus on where the state machine lives for connection requests and how you would make feed ranking pluggable. Compare your design with the walkthrough below.
Step 1: Identify Core Entities
Start by asking: what are the main "things" in this problem? Read through the requirements and underline nouns. Professional networking has more entity types than most LLD problems, so the key challenge is drawing clean boundaries between them.
A common mistake here is jamming everything into a giant User class. Good design means each class has a single, clear job.
| Entity | Responsibility | Key attributes |
|---|---|---|
| User | Identity and authentication. The actor in the system. | id, name, email, profile |
| Profile | Aggregates profile sections. Manages add/remove. | userId, education, experience, skills |
| Education | A single education entry. Value object. | school, degree, field, startYear, endYear |
| Experience | A single work experience entry. Value object. | company, title, description, startDate, endDate |
| Skill | A named skill with endorsement tracking. | name, endorsements |
| Endorsement | Records who endorsed which skill. Prevents duplicates. | endorserId, skillName, timestamp |
| ConnectionRequest | State machine for connecting two users. | senderId, receiverId, status, timestamp |
| Connection | Confirmed mutual link. | user1Id, user2Id, connectedAt |
| Post | Content published by a user. | authorId, content, visibility, reactions, comments |
| Comment | A response to a post. | authorId, postId, text, timestamp |
| Reaction | A user's reaction to a post (one per user per post). | userId, postId, reactionType |
| JobPosting | A job listed by a company. | companyId, title, requiredSkills, experienceLevel |
| JobApplication | Links a user to a job they applied for. | userId, jobId, status |
| Company | A business entity that posts jobs. | id, name, description |
| Message | A single message in a conversation. | senderId, conversationId, content |
| Conversation | A message thread between two connected users. | participant1Id, participant2Id, messages |
Notice we separated Profile from User because a profile is a rich aggregate with sections, while a User is just identity. We also made ConnectionRequest separate from Connection: a request is a state machine (PENDING, ACCEPTED, REJECTED, WITHDRAWN), while a Connection is the confirmed result.
Step 2: Define Relationships and Class Design
Deriving the ConnectionRequest state machine
Deriving state from requirements:
| Requirement | What ConnectionRequest must track |
|---|---|
| "User A sends a request to User B" | Sender, receiver, creation time |
| "User B can accept, reject, or ignore" | Current status with valid transitions |
| "Either party can withdraw a pending request" | Withdrawal as a transition from PENDING |
| "Prevent duplicate requests" | Status check before creating new requests |
The state machine is simple but the thread-safety is not. We need to synchronize on a canonical key (smaller user ID first) to prevent both requests from reaching PENDING simultaneously.
Deriving methods from needs:
| Need from requirements | Method |
|---|---|
| "Send a connection request" | sendRequest(senderId, receiverId) |
| "Accept/reject/withdraw" | accept(), reject(), withdraw() |
| "Prevent duplicates" | Check existing requests before creating |
| "Get connection degree" | getConnectionDegree(userId1, userId2) |
Deriving the Post interactions
We store reactions as a Map<String, ReactionType> so that when a user reacts again, it replaces the previous reaction. This is cheaper than scanning a list. The map also naturally enforces the "one reaction per user" invariant.
Key relationship decisions
- User owns Profile (composition) because a profile does not exist without a user.
- Skill aggregates Endorsements because endorsements are meaningless without the skill. The
Skillobject deduplicates by endorser ID. - Post aggregates Comments and Reactions because they are part of the post's identity.
- Conversation sits between two Users rather than being owned by one, because both participants have equal access.
Step 3: Choose Design Patterns
This is where the design comes together. We have four patterns to apply, each solving a distinct problem.
Pattern: Strategy for feed ranking
The signal: Three interchangeable feed algorithms (chronological, engagement-weighted, connection-proximity) with the same input and output.
Why Strategy over alternatives: If-else branches on a "feed type" enum means every new ranking algorithm edits the feed service. Strategy lets us add new algorithms by implementing an interface.
How it maps to our entities:
- Interface:
FeedRankingStrategy - Implementations:
ChronologicalStrategy,EngagementWeightedStrategy,ConnectionProximityStrategy
Pattern: Strategy for job matching
The signal: Multiple job matching criteria (skill-based, experience-level, location-based) that can be swapped or combined at runtime.
Why Strategy: Same reasoning as feed ranking. The matching algorithm varies independently of the job posting and user profile.
How it maps to our entities:
- Interface:
JobMatchingStrategy - Implementations:
SkillBasedMatcher,ExperienceLevelMatcher,LocationBasedMatcher
Pattern: Observer for notifications
Continue Reading with Premium
Unlock this article and every other in-depth system design guide on the platform with NotesFromSDE Premium.