Skip to main content

Design a Real-time Chat Application

Problem Statement

Design a real-time chat application that allows users to send and receive messages instantly. The system should support one-to-one messaging as well as group chats while handling millions of concurrent users efficiently.


Functional Requirements

  • Users should be able to send and receive messages in real time
  • Support one-to-one chat between users
  • Support group chats with multiple participants
  • Messages should be delivered only to active chat participants
  • The system should maintain chat history
  • Users should be able to join and leave group chats

Objects Required

  • User
  • Message
  • Chat (abstract)
  • PrivateChat
  • GroupChat
  • ChatService
  • MessageStatus

MessageStatus Enum


public enum MessageStatus {
    SENT,
    DELIVERED,
    READ
}

This enum keeps track of the state of a message as it moves through the system. A message starts as sent and can later be delivered or read depending on user activity.


User Class


import java.util.*;

public class User {

    private String userId;
    private String name;
    private boolean isOnline;

    public User(String userId, String name) {
        this.userId = userId;
        this.name = name;
        this.isOnline = true;
    }

    public void goOffline() {
        isOnline = false;
    }

    public void goOnline() {
        isOnline = true;
    }

    public boolean isOnline() {
        return isOnline;
    }

    public String getName() {
        return name;
    }
}

The constructor initializes a user with a unique id and name, and by default the user is marked online.

The goOffline() method updates the user status when they disconnect from the chat system.

The goOnline() method brings the user back into active state so they can receive messages again.

The isOnline() method helps the system decide whether messages should be delivered instantly or stored for later.


Message Class


import java.time.LocalDateTime;

public class Message {

    private String messageId;
    private User sender;
    private String content;
    private LocalDateTime timestamp;
    private MessageStatus status;

    public Message(String messageId, User sender, String content) {
        this.messageId = messageId;
        this.sender = sender;
        this.content = content;
        this.timestamp = LocalDateTime.now();
        this.status = MessageStatus.SENT;
    }

    public void markDelivered() {
        status = MessageStatus.DELIVERED;
    }

    public void markRead() {
        status = MessageStatus.READ;
    }

    public String getContent() {
        return content;
    }
}

The constructor creates a message with sender information and captures the exact time it was created. Every message starts in SENT state.

The markDelivered() method updates the message when it reaches the receiver’s device. This helps track delivery status in real time.

The markRead() method is used when the receiver actually opens and reads the message. This is useful for read receipts.

The getContent() method allows other components to access message text without exposing internal fields directly.


Chat (Abstract Class)


import java.util.*;

public abstract class Chat {

    protected String chatId;
    protected List participants;
    protected List messages;

    public Chat(String chatId) {
        this.chatId = chatId;
        this.participants = new ArrayList<>();
        this.messages = new ArrayList<>();
    }

    public void addUser(User user) {
        participants.add(user);
    }

    public void removeUser(User user) {
        participants.remove(user);
    }

    public void addMessage(Message message) {
        messages.add(message);
    }

    public List getMessages() {
        return messages;
    }
}

The constructor sets up a new chat with empty participants and message history.

The addUser() method allows a user to join a chat, whether it is private or group-based.

The removeUser() method removes a participant when they leave the chat.

The addMessage() method stores messages in the chat history so that the conversation can be retrieved later.

The getMessages() method returns the full chat history for display or debugging purposes.


PrivateChat Class


public class PrivateChat extends Chat {

    public PrivateChat(String chatId) {
        super(chatId);
    }

    public void sendMessage(Message message) {
        addMessage(message);
    }
}

The constructor simply initializes a one-to-one chat using the base Chat class.

The sendMessage() method adds a message directly to the chat. Since this is a private chat, there is no extra routing logic involved.


GroupChat Class


public class GroupChat extends Chat {

    private String groupName;

    public GroupChat(String chatId, String groupName) {
        super(chatId);
        this.groupName = groupName;
    }

    public void sendMessage(Message message) {
        addMessage(message);
    }

    public String getGroupName() {
        return groupName;
    }
}

The constructor initializes a group chat with a name and chat id.

The sendMessage() method adds a message to the group conversation so that all participants can access it.

The getGroupName() method returns the name of the group for display purposes in UI or logs.


ChatService Class


import java.util.*;

public class ChatService {

    private List chats;

    public ChatService() {
        this.chats = new ArrayList<>();
    }

    public Chat createPrivateChat(User u1, User u2) {

        Chat chat = new PrivateChat(UUID.randomUUID().toString());

        chat.addUser(u1);
        chat.addUser(u2);

        chats.add(chat);

        return chat;
    }

    public GroupChat createGroupChat(String name, List users) {

        GroupChat group = new GroupChat(UUID.randomUUID().toString(), name);

        for (User user : users) {
            group.addUser(user);
        }

        chats.add(group);

        return group;
    }

    public void sendMessage(Chat chat, Message message) {
        chat.addMessage(message);
    }
}

The constructor initializes the system with an empty list of chats.

The createPrivateChat() method creates a one-to-one chat between two users and registers it inside the system.

The createGroupChat() method creates a group, adds all users, and stores it in the chat list so it can be tracked later.

The sendMessage() method routes a message into the correct chat. In a real system, this is where WebSocket or push notification logic would sit.


Main Class


import java.util.*;

public class Main {

    public static void main(String[] args) {

        User u1 = new User("U1", "Alice");
        User u2 = new User("U2", "Bob");
        User u3 = new User("U3", "Charlie");

        ChatService service = new ChatService();

        Chat privateChat = service.createPrivateChat(u1, u2);

        GroupChat groupChat = service.createGroupChat(
                "Dev Group",
                Arrays.asList(u1, u2, u3)
        );

        Message m1 = new Message("M1", u1, "Hey Bob!");

        service.sendMessage(privateChat, m1);

        Message m2 = new Message("M2", u2, "Hello everyone");

        service.sendMessage(groupChat, m2);

        System.out.println("Messages sent successfully");
    }
}

The main method simulates a simple flow where users are created and added into private and group chats.

Messages are then sent through the ChatService which handles routing into the correct chat instance.

This gives a basic but realistic view of how a chat system would behave at a high level.


Class Diagram

UseruserId : Stringname : StringisOnline : booleangoOffline() : voidgoOnline() : voidisOnline() : booleangetName() : StringMessagemessageId : Stringsender : Usercontent : Stringtimestamp : LocalDateTimestatus : MessageStatusmarkDelivered() : voidmarkRead() : voidgetContent() : StringChatchatId : Stringparticipants : List<User>messages : List<Message>addUser(user : User) : voidremoveUser(user : User) : voidaddMessage(message : Message) : voidgetMessages() : List<Message>PrivateChatsendMessage(message : Message) : voidGroupChatgroupName : StringsendMessage(message : Message) : voidgetGroupName() : StringChatServicechats : List<Chat>createPrivateChat(u1 : User, u2 : User) : ChatcreateGroupChat(name : String, users : List<User>) : GroupChatsendMessage(chat : Chat, message : Message) : voidMessageStatusSENTDELIVEREDREAD

Also See

Comments

Popular posts from this blog

Designing a Parking Lot - Low Level Design

Problem Statement Design a parking lot that can handle vehicles entering and leaving while managing parking across multiple floors. Each vehicle should be assigned a suitable parking spot based on its type, and the spot should be freed once the vehicle exits. The design should also support generating a ticket at entry and optionally calculating the parking fee based on the duration of stay. Asked In Companies Amazon Google Microsoft Uber Walmart Flipkart Meta PayPal Oracle Salesforce Adobe Apple Intuit LinkedIn Atlassian Functional Requirements The design should support multiple vehicle types such as bikes, cars, and trucks A vehicle must be assigned a parking spot compatible with its type A parking spot cannot be assigned to more than one vehicle at a time The parking lot should support multiple levels (floors) The design should search and allocate an availa...

Most Frequently Asked Low Level Design(LLD) Interview Questions

Below are the curated list of most commonly asked Low Level Design (LLD) interview problems. Each problem includes a short description and a link to the complete solution with code and class diagrams. Design Parking Lot System The system should handle parking for different vehicle types such as bikes, cars, and trucks. It should manage slot allocation, availability tracking, and entry/exit flow. The design also ensures efficient usage of parking space under varying load conditions. View Solution Design Elevator / Lift System The system should support multiple elevators operating across floors with request handling logic. It focuses on scheduling algorithms to minimize wait time and optimize movement. It also manages direction control and concurrent floor requests. View Solution Design Movie Ticket Booking System The system should allow users to browse movies, select shows, and book seats. It handles seat ...

Software Design Patterns for LLD Interviews: A Complete Guide

Software Design Patterns for LLD Interviews: A Complete Guide In Software Development Engineer (SDE) interviews—especially for mid-level and senior roles—low-level design (LLD) rounds assess your ability to write clean, reusable, maintainable, and extensible code. The foundation of resolving these architectural challenges lies in the standard Gang of Four (GoF) Design Patterns. Rather than memorizing theoretical definitions, interviewers expect you to apply these patterns to real-world scenarios, identifying the trade-offs of each. Below is a comprehensive guide to the 12 most frequently asked design patterns in LLD interviews, categorized by their classification (Creational, Structural, and Behavioral). Each pattern contains a concrete, real-world Java implementation and a detailed breakdown of design decisions. Creational Design Patterns Creational design patterns deal with object creation mechanisms. They abstract the instantiation process, making a system independent of how...