LLD Hub
lldobservercomposite-pattern

How to Design a Chat Application (WhatsApp) | LLD Guide

Design a WhatsApp-like messaging system — 1-on-1 chats, group chats, message status, Observer pattern for real-time delivery. Common at Slack, Meta, and Zomato.

6 April 2025·8 min read

Practice this problem

Chat Application (WhatsApp-like) — get AI-scored feedback on your solution

Solve it →

Designing a chat application like WhatsApp is a popular LLD problem that tests your ability to model real-time messaging, group management, message delivery status, and media handling. It's frequently asked at Slack, Meta, Zomato, and Swiggy.

Core Requirements

  • One-on-one messaging
  • Group chats with member management
  • Message status: Sent → Delivered → Read
  • Online/offline user status
  • Send text, images, documents
  • Message history and search

Core Entities

  • User — profile, online status, last seen
  • Chat — abstract; either DirectChat or GroupChat (Composite pattern)
  • Message — content, sender, timestamp, status, type (Text/Image/Document)
  • MessageStatus — SENT, DELIVERED, READ per recipient
  • GroupMembership — user, group, role (Admin/Member), joinedAt

Composite Pattern for Chat Types

interface Chat {
  getId(): string;
  sendMessage(msg: Message): void;
  getMessages(limit: number): Message[];
  getParticipants(): User[];
}

class DirectChat implements Chat {
  participants: [User, User];
  sendMessage(msg: Message) {
    this.messageStore.save(this.id, msg);
    this.notifyParticipants(msg);
  }
}

class GroupChat implements Chat {
  name: string;
  members: GroupMembership[];
  maxMembers = 256;

  addMember(user: User, addedBy: User): void {
    if (this.members.length >= this.maxMembers) throw new Error("Group full");
    const adder = this.getMembership(addedBy);
    if (!adder?.isAdmin()) throw new Error("Only admins can add members");
    this.members.push(new GroupMembership(user, this));
  }
}

Message Delivery Status

class Message {
  id: string;
  content: string;
  sender: User;
  type: MessageType;  // TEXT | IMAGE | DOCUMENT
  sentAt: Date;
  statusPerRecipient: Map<string, MessageStatus>;

  markDelivered(userId: string) {
    this.statusPerRecipient.set(userId, MessageStatus.DELIVERED);
    if (this.allDelivered()) this.overallStatus = MessageStatus.DELIVERED;
  }

  markRead(userId: string) {
    this.statusPerRecipient.set(userId, MessageStatus.READ);
    if (this.allRead()) this.overallStatus = MessageStatus.READ;
  }
}

Observer for Real-time Delivery

class MessageBroker {
  private subscribers = new Map<string, MessageHandler[]>();

  subscribe(userId: string, handler: MessageHandler) {
    this.subscribers.get(userId)?.push(handler) ??
      this.subscribers.set(userId, [handler]);
  }

  deliver(message: Message, recipients: User[]) {
    recipients.forEach(user => {
      const handlers = this.subscribers.get(user.id) ?? [];
      if (handlers.length > 0) {
        handlers.forEach(h => h.handle(message));
        message.markDelivered(user.id);
      }
      // User offline: queue for when they come online
    });
  }
}

Common Questions

  • "How do messages reach offline users?" → Store in a message queue (per-user inbox); deliver on reconnect
  • "How does read receipt work in groups?" → MessageStatus tracks per-recipient; "Read" shows when ALL have read
  • "How to support 256-member groups?" → GroupChat stores membership list, fan-out on send
  • "How to search messages?" → Full-text index on message content, scoped to chat ID

Ready to practice?

Submit your solution and get AI-scored feedback on OOP, SOLID principles, design patterns, and code quality.

Solve Chat Application (WhatsApp-like)