Bird
Raised Fist0

Design a Messaging System OOP - User, Conversation, Message, Delivery Status

LLDmediumInterview freq: high
Choose your preparation mode4 modes available
🏗️
Design a Messaging System OOP - User, Conversation, Message, Delivery Status
Design a scalable, extensible messaging system with users, conversations, messages, and delivery status tracking using Java OOP principles.
mediumSocial and Professional PlatformsFreq: high
Prerequisites:OOP FundamentalsJava Concurrency BasicsDesign Patterns (Factory, Observer)SOLID Principles
⚙️
Functional Requirements
  • Support one-to-one and group conversations
  • Track delivery status per message per user (sent, delivered, read)
  • Allow users to send, edit, and delete messages
  • Maintain conversation participant list with add/remove functionality
  • Ensure message ordering within a conversation
  • Support concurrent message sending and reading
  • Notify users of new messages in conversations they participate in
👤
Use Cases
  • User sends a message to a conversation
  • User creates a new conversation
  • User reads messages in a conversation
  • System tracks delivery status of messages
  • User adds/removes participants in a conversation
  • User receives notifications for new messages
🧑‍💻
Actors
  • User
  • System Administrator
  • Notification Service
❓
Clarifying Questions to Ask
  • How many concurrent threads/users do we expect sending messages simultaneously?
  • Should delivery status updates be real-time or eventually consistent?
  • Are message edits and deletes allowed after sending? If yes, how to handle concurrency?
  • Is there a limit on conversation size or number of participants?
  • How to handle message ordering in distributed environment?
  • What is the pricing strategy for message storage or delivery?
  • How to handle edge cases like user leaving conversation while messages are in transit?
Out of Scope
✗ Message encryption and security
✗ Media attachments in messages
✗ Offline message storage and sync
✗ Search functionality within messages
✗ Pricing or monetization strategies
⚡
QPS Calculation
write qps5000
read qps20000
concurrent users100000
🖥️
Server Estimate
app servers20
db servers5
notification servers5
🔢
Enumerations
enum DeliveryStatus
SENT | DELIVERED | READ
Enum prevents typo bugs and enables exhaustive switch statements for delivery status handling.
enum MessageType
TEXT | IMAGE | VIDEO
Enum allows extensible message types and type-safe handling of different message content.
enum UserStatus
ONLINE | OFFLINE | AWAY
Enum captures user presence states for notification and UI updates.
📦
User
3 fields
private final String userIdprivate String displayNameprivate volatile UserStatus status
Represents a system user with identity, display name, and presence status.
public String getUserId()Returns the unique identifier of the user.
public synchronized void setStatus(UserStatus status)Atomically updates the user's presence status.
public UserStatus getStatus()Returns the current presence status of the user.
📦
Message
6 fields
private final String messageIdprivate final String senderIdprivate final long timestampprivate final MessageType typeprivate String contentprivate final Map<String, DeliveryStatus> deliveryStatusMap
Represents a message sent by a user with content, type, timestamp, and per-recipient delivery status.
public synchronized void updateDeliveryStatus(String userId, DeliveryStatus status)Atomically updates the delivery status for a specific user.
public synchronized DeliveryStatus getDeliveryStatus(String userId)Returns the delivery status for a specific user.
public synchronized void editContent(String newContent)Allows editing the message content atomically.
📦
Conversation
3 fields
private final String conversationIdprivate final Set<String> participantIdsprivate final List<Message> messages
Manages participants and messages within a conversation, ensuring message ordering and participant management.
public synchronized boolean addParticipant(String userId)Atomically adds a user to the conversation participants.
public synchronized boolean removeParticipant(String userId)Atomically removes a user from the conversation participants.
public synchronized void addMessage(Message message)Atomically appends a new message to the conversation.
public synchronized List<Message> getMessages()Returns a copy of the messages list to preserve encapsulation.
📦
DeliveryStatusTracker
1 fields
private final Map<String, Map<String, DeliveryStatus>> conversationMessageStatus
Tracks delivery status of messages per user per conversation, enabling efficient status updates and queries.
public synchronized void updateStatus(String conversationId, String messageId, String userId, DeliveryStatus status)Atomically updates delivery status for a message and user within a conversation.
public synchronized DeliveryStatus getStatus(String conversationId, String messageId, String userId)Returns the delivery status for a given message and user.
📦
NotificationService
1 fields
private final ExecutorService notificationExecutor
Sends asynchronous notifications to users about new messages or status changes.
Why thisUses Java ExecutorService for asynchronous task execution, enabling scalable notification dispatch.
Why notAvoided synchronous notification sending to prevent blocking user operations; alternatives like external message queues (Kafka) could be used but add complexity.
public void notifyUser(String userId, String message)Asynchronously sends a notification message to a user.
🔗
Relationships
Conversation◆──►Message[1..*]
compositionMessages cannot exist without Conversation; destroyed with Conversation
Conversation◇──►User[1..*]
aggregationUsers exist independently; Conversation holds references
Message──►DeliveryStatus[1..*]
associationDeliveryStatus is a value object, no independent lifecycle
DeliveryStatusTracker──►Conversation[1..1]
associationTracker references Conversation but does not own it
NotificationService──►User[1..*]
associationNotificationService sends notifications to Users; no ownership
🗄️
Messages
PK: conversationId
messageIdsenderIdtypecontenttimestamp
Stores messages per conversation ordered by timestamp for efficient retrieval.
🗄️
ConversationParticipants
PK: conversationId
userIdjoinedAt
Stores participants of each conversation with join timestamps.
🔌
SendMessage
Request
{
  "conversationId": "String",
  "senderId": "String",
  "messageType": "MessageType",
  "content": "String"
}
Response
{
  "messageId": "String",
  "status": "String"
}
Sends a message to a conversation and returns message ID and status.
🔌
GetMessages
Request
{
  "conversationId": "String",
  "limit": "int",
  "offset": "int"
}
Response
{
  "messages": "List<Message>"
}
Retrieves paginated messages from a conversation.
🔌
UpdateDeliveryStatus
Request
{
  "conversationId": "String",
  "messageId": "String",
  "userId": "String",
  "status": "DeliveryStatus"
}
Response
{
  "success": "boolean"
}
Updates delivery status for a message for a specific user.

Practice

(1/5)
1. A ParkingLot class has a private constructor and a static synchronized getInstance() method returning the single instance. Which design pattern is applied here and why is this the correct use case?
easy
A. Prototype pattern, to clone ParkingLot instances efficiently.
B. Factory pattern, because it controls object creation centrally.
C. Builder pattern, to construct complex ParkingLot objects step-by-step.
D. Singleton pattern, to ensure only one ParkingLot instance manages all spots.

Solution

  1. Step 1: Identify the pattern from constructor and method

    The private constructor prevents external instantiation, and the static synchronized getInstance() method ensures a single instance is created and accessed globally.
  2. Step 2: Why Singleton fits ParkingLot

    ParkingLot must be unique to coordinate spot allocation and pricing consistently across the system, so Singleton enforces this single shared instance.
  3. Final Answer:

    Option D -> Option D
  4. Quick Check:

    Private constructor + synchronized static getInstance() -> Singleton pattern [check]
Hint: Private constructor + static getInstance() = Singleton [check]
Common Mistakes:
2. In the Restaurant Management System, the OrderManager class has a private constructor and a static synchronized getInstance() method. Which design pattern is applied here and why is it the correct choice?
easy
A. Factory pattern, because it controls object creation centrally.
B. Builder pattern, to incrementally construct complex orders.
C. Singleton pattern, to ensure only one instance manages all orders globally.
D. Prototype pattern, to clone existing orders efficiently.

Solution

  1. Step 1: Identify the pattern from constructor and method

    The private constructor prevents external instantiation, and the static synchronized getInstance() method ensures a single shared instance, which matches the Singleton pattern.
  2. Step 2: Why Singleton fits OrderManager

    OrderManager must coordinate all orders centrally to avoid conflicting states; multiple instances would cause inconsistent order tracking.
  3. Final Answer:

    Option C -> Option C
  4. Quick Check:

    Private constructor + synchronized static getInstance() -> Singleton [check]
Hint: Private constructor + static getInstance() -> Singleton [check]
Common Mistakes:
3. In the shopping cart system, should Discount be designed as an abstract class or an interface? Consider that discounts have varying calculation logic but share no common state.
easy
A. Interface, because Java interfaces can hold state and behavior since Java 8.
B. Abstract class, because discounts share common fields like discountAmount.
C. Abstract class, to enforce a default implementation of applyDiscount().
D. Interface, because discounts only define calculation behavior without shared state.

Solution

  1. Step 1: Analyze shared state vs behavior

    Discounts vary in calculation logic and do not share common fields, so no shared state exists.
  2. Step 2: Choose interface for behavior without state

    Interfaces define behavior contracts without enforcing state, fitting varying discount calculations better than abstract classes.
  3. Final Answer:

    Option D -> Option D
  4. Quick Check:

    No shared state + varying behavior -> interface [check]
Hint: No shared state + varying logic -> interface [check]
Common Mistakes:
4. A class has a private constructor and a static synchronized getInstance() method that returns the single instance. Which design pattern is applied and why is this the CORRECT use case?
easy
A. Factory Pattern, because it controls object creation centrally.
B. Prototype Pattern, because it clones existing instances to avoid new creation.
C. Builder Pattern, because it constructs complex objects step-by-step.
D. Singleton Pattern, because it ensures only one instance with thread-safe lazy initialization.

Solution

  1. Step 1: Identify the pattern from private constructor and static synchronized method

    The private constructor prevents external instantiation, and the static synchronized getInstance() method ensures a single instance is created lazily and safely in multithreaded contexts.
  2. Step 2: Eliminate other creational patterns

    Factory controls creation but allows multiple instances; Builder separates construction steps; Prototype clones instances. None enforce single instance with thread safety.
  3. Final Answer:

    Option D -> Option D
  4. Quick Check:

    Private constructor + synchronized getInstance() -> thread-safe Singleton [check]
Hint: Private constructor + synchronized getInstance() -> Singleton [check]
Common Mistakes:
5. In the Hotel Management System, if the parkVehicle() method is not synchronized, which exact sequence of events between Thread 1 and Thread 2 causes double-booking of the same parking spot?
medium
A. Thread 1 checks spot availability -> true, context switch before marking occupied; Thread 2 checks availability -> true, both park simultaneously.
B. Thread 1 marks spot occupied, Thread 2 reads spot as occupied and waits indefinitely.
C. Thread 1 parks vehicle, Thread 2 waits until Thread 1 finishes, then parks another vehicle.
D. Thread 1 and Thread 2 both crash due to simultaneous access causing deadlock.

Solution

  1. Step 1: Identify the check-then-act race condition

    Without synchronization, Thread 1 reads spot as available and is preempted before marking it occupied.
  2. Step 2: Thread 2 also reads spot as available and proceeds to park, causing double-booking.

    This is the classic check-then-act gap causing race condition.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Check availability -> true by both threads before marking occupied -> double-booking [check]
Hint: Check-then-act gap -> double-booking without synchronization [check]
Common Mistakes:
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard