Designing WhatsApp - Low Level Design
On this page
Designing WhatsApp - Low Level Design
Problem Statement
Design a Chat application like WhatsApp that allows users to send and receive messages in real time. The system should support one-on-one conversations as well as group chats. Each message should be associated with a sender, a timestamp, and a delivery status. The design should also handle user registration, chat history retrieval, and basic group management like adding or removing members.
Asked In Companies
Amazon Google Microsoft Meta Uber Flipkart LinkedIn Atlassian Apple Salesforce
Functional Requirements
- Users should be able to register and have a unique identity in the system
- A user should be able to send a message to another user (one-on-one chat)
- A user should be able to create a group and add or remove members
- Messages sent to a group should be delivered to all members
- Each message should carry a timestamp and a delivery status (SENT, DELIVERED, READ)
- A user should be able to fetch the full chat history of any conversation
- The design should distinguish between individual chats and group chats
Objects Required
- User
- Message
- MessageStatus
- Chat
- IndividualChat
- GroupChat
- ChatApp
User Class
The User class represents a person using WhatsApp.
import java.util.*;
public class User {
private String userId;
private String name;
private List<Chat> chats;
public User(String userId, String name) {
this.userId = userId;
this.name = name;
this.chats = new ArrayList<>();
}
public String getUserId() {
return userId;
}
public String getName() {
return name;
}
public List<Chat> getChats() {
return chats;
}
public void addChat(Chat chat) {
chats.add(chat);
}
}
The constructor takes a userId and a name and initializes an empty list of chats. Every user starts with no conversations, and chats get added over time as they participate in them.
getUserId() returns the unique identifier for this user. Other classes use this when looking up who sent a message or who belongs to a group.
getName() is a simple accessor used when displaying sender information in a chat history.
getChats() returns all chats this user is part of, both individual and group. The calling code can iterate this list to show the user’s inbox.
addChat() links a new conversation to this user. This is called by ChatApp when a new chat is created so that both participants have a reference to it.
MessageStatus Enum
public enum MessageStatus {
SENT,
DELIVERED,
READ
}
This enum captures the lifecycle of a message in WhatsApp. SENT means the sender’s side has dispatched it. DELIVERED means it reached the recipient’s device. READ means the recipient has opened and seen it. Using an enum here avoids magic strings and makes status transitions easy to reason about.
Message Class
The Message class represents a single message exchanged in WhatsApp.
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 User getSender() {
return sender;
}
public String getContent() {
return content;
}
public LocalDateTime getTimestamp() {
return timestamp;
}
public MessageStatus getStatus() {
return status;
}
public void setStatus(MessageStatus status) {
this.status = status;
}
}
The constructor captures the sender and content immediately and sets the timestamp to the current time. The status defaults to SENT because at the moment of creation, the message has only been dispatched.
getSender() is used when rendering the chat history to know who wrote each message.
getContent() returns the actual text body of the message.
getTimestamp() is used for ordering messages chronologically and for displaying time labels in the chat UI.
getStatus() lets the caller check whether the message has been delivered or read.
setStatus() allows the system to update the delivery status as the message moves through its lifecycle, for example, from SENT to DELIVERED once the other side comes online.
Chat Abstract Class
The Chat class is defined as abstract because both individual and group conversations in WhatsApp share common behavior. They both hold a list of messages and allow messages to be sent and retrieved, but their internal logic differs.
import java.util.*;
public abstract class Chat {
protected String chatId;
protected List<Message> messages;
public Chat(String chatId) {
this.chatId = chatId;
this.messages = new ArrayList<>();
}
public String getChatId() {
return chatId;
}
public List<Message> getMessages() {
return messages;
}
public abstract void sendMessage(User sender, String content);
}
The constructor assigns a unique ID and initializes an empty message list.
getChatId() returns the identifier for this conversation. This is useful when the app needs to look up a specific chat.
getMessages() returns the full history of messages exchanged in this chat. The caller can use this to render the conversation thread.
sendMessage() is declared abstract because the behavior differs. In a one-on-one chat you just append the message, but in a group chat you may want to broadcast it to all members. Each subclass provides its own implementation.
IndividualChat Class
IndividualChat represents a private conversation between exactly two users in WhatsApp.
import java.util.UUID;
public class IndividualChat extends Chat {
private User participant1;
private User participant2;
public IndividualChat(String chatId, User participant1, User participant2) {
super(chatId);
this.participant1 = participant1;
this.participant2 = participant2;
}
@Override
public void sendMessage(User sender, String content) {
Message message = new Message(UUID.randomUUID().toString(), sender, content);
messages.add(message);
}
}
The constructor calls super(chatId) to initialize the shared state and then stores the two participants.
sendMessage() creates a new Message object with a generated ID, the sender reference, and the content, then appends it to the message list. The simplicity here is intentional. Individual chat has no routing logic. The message just goes into the shared history that both participants read from.
GroupChat Class
GroupChat handles group conversations in WhatsApp with multiple participants.
import java.util.*;
public class GroupChat extends Chat {
private String groupName;
private List<User> members;
public GroupChat(String chatId, String groupName) {
super(chatId);
this.groupName = groupName;
this.members = new ArrayList<>();
}
public void addMember(User user) {
members.add(user);
}
public void removeMember(User user) {
members.remove(user);
}
public List<User> getMembers() {
return members;
}
@Override
public void sendMessage(User sender, String content) {
Message message = new Message(UUID.randomUUID().toString(), sender, content);
messages.add(message);
}
}
The constructor takes a group name alongside the chat ID and initializes an empty member list.
addMember() adds a user to the WhatsApp group. This is called when the group is created or when an existing member invites someone new.
removeMember() removes a user from the group’s member list. Once removed, that user would no longer receive new messages, enforced at the application layer.
getMembers() returns the current list of group participants. This is used when the UI needs to show the member count or list.
sendMessage() in the group context still appends to the shared message list. All members read from the same list, so there is no need to duplicate the message. The single list acts as the shared broadcast channel.
ChatApp Class
ChatApp is the entry point that coordinates the entire WhatsApp design.
import java.util.*;
public class ChatApp {
private Map<String, User> users;
private Map<String, Chat> chats;
public ChatApp() {
this.users = new HashMap<>();
this.chats = new HashMap<>();
}
public User registerUser(String userId, String name) {
User user = new User(userId, name);
users.put(userId, user);
return user;
}
public IndividualChat createIndividualChat(String chatId, User user1, User user2) {
IndividualChat chat = new IndividualChat(chatId, user1, user2);
chats.put(chatId, chat);
user1.addChat(chat);
user2.addChat(chat);
return chat;
}
public GroupChat createGroupChat(String chatId, String groupName, List<User> members) {
GroupChat chat = new GroupChat(chatId, groupName);
for (User member : members) {
chat.addMember(member);
member.addChat(chat);
}
chats.put(chatId, chat);
return chat;
}
public void sendMessage(Chat chat, User sender, String content) {
chat.sendMessage(sender, content);
}
public List<Message> getChatHistory(Chat chat) {
return chat.getMessages();
}
}
The constructor initializes two maps, one for users and one for chats, to enable quick lookup by ID.
registerUser() creates a new User object and stores it in the users map. This is the first step before any user can send or receive messages on WhatsApp.
createIndividualChat() creates a private conversation between two users and adds the chat to both users’ chat lists. This ensures both sides can find the conversation from their own profile.
createGroupChat() creates a WhatsApp group conversation, adds all the initial members to it, and links the chat to each member’s list. Iterating over the members at creation time means no one gets left out.
sendMessage() delegates the actual message creation to the chat object. The app layer just routes the call and the chat knows how to handle it internally.
getChatHistory() returns the list of messages from a given chat. This is what the frontend would call to render a conversation thread.
Main Function
import java.util.*;
public class Main {
public static void main(String[] args) {
ChatApp app = new ChatApp();
// Register users
User alice = app.registerUser("u1", "Alice");
User bob = app.registerUser("u2", "Bob");
User charlie = app.registerUser("u3", "Charlie");
// One-on-one WhatsApp chat between Alice and Bob
IndividualChat aliceBobChat = app.createIndividualChat("c1", alice, bob);
app.sendMessage(aliceBobChat, alice, "Hey Bob!");
app.sendMessage(aliceBobChat, bob, "Hey Alice! What's up?");
// WhatsApp group with all three
List<User> groupMembers = Arrays.asList(alice, bob, charlie);
GroupChat groupChat = app.createGroupChat("c2", "Friends", groupMembers);
app.sendMessage(groupChat, alice, "Hello everyone!");
app.sendMessage(groupChat, charlie, "Hey Alice!");
// Print chat history of Alice and Bob
System.out.println("=== Alice & Bob Chat ===");
for (Message msg : app.getChatHistory(aliceBobChat)) {
System.out.println(msg.getSender().getName() + ": " + msg.getContent()
+ " [" + msg.getStatus() + "]");
}
// Print WhatsApp group chat history
System.out.println("\n=== Friends Group Chat ===");
for (Message msg : app.getChatHistory(groupChat)) {
System.out.println(msg.getSender().getName() + ": " + msg.getContent()
+ " [" + msg.getStatus() + "]");
}
}
}
The main function demonstrates the full WhatsApp flow. Three users are registered first. Alice and Bob get a private chat where they exchange two messages. Then all three are added to a group called “Friends” and two more messages are sent there. Finally, both chat histories are printed with the sender name, content, and message status, simulating what a basic WhatsApp interface would display.