π¬ Machine Coding: Online Chat System
π Overview
A foundational backend framework for a real-time messaging application like WhatsApp or Discord. It handles user management, friend request workflows, and routes messages across both 1-on-1 private chats and multi-user group chats.
Why This Challenge?
- Entity Relationships: Tests your ability to handle complex, many-to-many relationships (users to chats, users to friends).
- Polymorphism: Evaluates how well you can abstract communication channels so the system treats Private and Group chats interchangeably.
- State Management: Requires cleanly mapping lifecycle transitions, such as friend requests moving from
UNREADtoACCEPTED.
π The Scenario & Requirements
π‘ The Problem (The Villain)
"The Spaghetti Network." Chat applications quickly become unmaintainable when User objects are allowed to directly mutate each other's states. If User A adds User B as a friend, but the operation crashes halfway through, User A thinks they are friends while User B does not. Furthermore, if group chats and private chats are built as entirely separate systems, message routing logic becomes horribly duplicated.
π¦Έ The System (The Hero)
"The Unified Router." A centralized UserService acts as the orchestrator, guaranteeing transactional safety when connecting users. The system relies on an abstract Chat base class, allowing messages to be pushed to a single friend or broadcasted to a room of 50 people using the exact same add_message() pipeline.
π Requirements & Constraints
- (Functional): Users can send, receive, and approve/reject friend requests.
- (Functional): The system must support both Private (1-on-1) and Group (1-to-N) chats.
- (Functional): Users can add or remove other users from Group chats.
- (Technical): Chat history (
Messageobjects) must be encapsulated safely within theChatobjects, not stored raw on theUser.
ποΈ Design & Architecture
π§ Thinking Process
To prevent tight coupling, we need to separate the participants, the medium, and the manager:
1. The Atomic Unit: Message (ID, text, timestamp).
2. The Medium: An abstract Chat class holding a list of Users and Messages. This is subclassed into PrivateChat (strictly 2 users) and GroupChat (dynamic user list).
3. The Participant: User, holding their own friend lists and pending requests, but forbidden from modifying other users.
4. The Orchestrator: UserService. It coordinates complex interactions, like establishing a friendship by cross-updating two distinct User objects simultaneously.
π§© Class Diagram
(The Object-Oriented Blueprint. Who owns what?)
classDiagram
direction TB
class UserService {
-Dict users_by_id
+add_user()
+send_friend_request()
+approve_friend_request()
}
class User {
+String user_id
+Dict friends_by_id
+receive_friend_request()
+send_friend_request()
}
class Chat {
<<abstract>>
+String chat_id
-List~Message~ messages
-List~User~ users
+add_message()
}
class PrivateChat
class GroupChat {
+add_user()
+remove_user()
}
class Message
UserService o-- User : manages
User o-- Chat : participates in
Chat *-- Message : contains
Chat <|-- PrivateChat
Chat <|-- GroupChat
βοΈ Design Patterns Applied
- Mediator Pattern (via
UserService): Acts as a mediator for complex multi-user interactions. Instead ofUserA.add_friend(UserB), they ask theUserService, which orchestrates the state changes for both, preventing corrupted states. - Template / Strategy Pattern (via
ChatPolymorphism): BothPrivateChatandGroupChatexpose the same core mechanism (add_message), hiding the underlying complexity of how many users are actually in the room.
π» Solution Implementation
The Code
from abc import ABC
from datetime import datetime
from enum import Enum
from typing import Dict, List
class RequestStatus(Enum):
"""Represents the status of a friend request."""
UNREAD = 0
READ = 1
ACCEPTED = 2
REJECTED = 3
class Message:
"""Represents a single text message within a chat."""
def __init__(self, message_id: str, message: str, timestamp: datetime) -> None:
"""
Initializes a Message.
Args:
message_id (str): Unique identifier for the message.
message (str): The content of the message.
timestamp (datetime): The time the message was sent.
"""
self.message_id: str = message_id
self.message: str = message
self.timestamp: datetime = timestamp
class AddRequest:
"""Represents a friend request from one user to another."""
def __init__(self, from_user_id: str, to_user_id: str, request_status: RequestStatus, timestamp: datetime) -> None:
"""
Initializes an AddRequest.
Args:
from_user_id (str): ID of the user sending the request.
to_user_id (str): ID of the user receiving the request.
request_status (RequestStatus): Current status of the request.
timestamp (datetime): When the request was created.
"""
self.from_user_id: str = from_user_id
self.to_user_id: str = to_user_id
self.request_status: RequestStatus = request_status
self.timestamp: datetime = timestamp
class User:
"""Represents a user in the chat system."""
def __init__(self, user_id: str, name: str, pass_hash: str) -> None:
"""
Initializes a User.
Args:
user_id (str): Unique identifier for the user.
name (str): Display name of the user.
pass_hash (str): Hashed password for authentication.
"""
self.user_id: str = user_id
self.name: str = name
self.pass_hash: str = pass_hash
self.friends_by_id: Dict[str, 'User'] = {}
self.private_chats_by_friend_id: Dict[str, 'PrivateChat'] = {}
self.group_chats_by_id: Dict[str, 'GroupChat'] = {}
self.received_friend_requests: Dict[str, AddRequest] = {}
self.sent_friend_requests: Dict[str, AddRequest] = {}
def receive_friend_request(self, request: AddRequest) -> None:
"""Stores an incoming friend request."""
self.received_friend_requests[request.from_user_id] = request
def send_friend_request(self, request: AddRequest) -> None:
"""Stores an outgoing friend request."""
self.sent_friend_requests[request.to_user_id] = request
class Chat(ABC):
"""Abstract base class representing a generic chat channel."""
def __init__(self, chat_id: str) -> None:
"""
Initializes a Chat.
Args:
chat_id (str): Unique identifier for the chat.
"""
self.chat_id: str = chat_id
self.users: List[User] = []
self.messages: List[Message] = []
def add_message(self, message: Message) -> None:
"""Appends a new message to the chat history."""
self.messages.append(message)
class PrivateChat(Chat):
"""Represents a 1-on-1 chat between two users."""
def __init__(self, chat_id: str, user1: User, user2: User) -> None:
"""
Initializes a PrivateChat.
Args:
chat_id (str): Unique identifier for the chat.
user1 (User): The first participant.
user2 (User): The second participant.
"""
super().__init__(chat_id)
self.users.extend([user1, user2])
class GroupChat(Chat):
"""Represents a multi-user group chat."""
def __init__(self, chat_id: str) -> None:
super().__init__(chat_id)
def add_user(self, user: User) -> None:
"""Adds a user to the group chat."""
if user not in self.users:
self.users.append(user)
def remove_user(self, user: User) -> None:
"""Removes a user from the group chat."""
if user in self.users:
self.users.remove(user)
class UserService:
"""Service class responsible for managing user interactions and routing."""
def __init__(self) -> None:
self.users_by_id: Dict[str, User] = {}
def add_user(self, user_id: str, name: str, pass_hash: str) -> User:
"""Registers a new user in the system."""
user = User(user_id, name, pass_hash)
self.users_by_id[user_id] = user
return user
def remove_user(self, user_id: str) -> None:
"""Removes a user from the system."""
self.users_by_id.pop(user_id, None)
def send_friend_request(self, from_user_id: str, to_user_id: str) -> None:
"""Orchestrates sending a friend request between two users."""
from_user = self.users_by_id.get(from_user_id)
to_user = self.users_by_id.get(to_user_id)
if from_user and to_user:
request = AddRequest(from_user_id, to_user_id, RequestStatus.UNREAD, datetime.now())
from_user.send_friend_request(request)
to_user.receive_friend_request(request)
def approve_friend_request(self, from_user_id: str, to_user_id: str) -> None:
"""Approves a pending friend request and establishes the connection."""
from_user = self.users_by_id.get(from_user_id)
to_user = self.users_by_id.get(to_user_id)
if from_user and to_user:
request = to_user.received_friend_requests.get(from_user_id)
if request:
request.request_status = RequestStatus.ACCEPTED
from_user.friends_by_id[to_user_id] = to_user
to_user.friends_by_id[from_user_id] = from_user
π¬ Why This Works (Evaluation)
The architecture heavily leverages the Dependency Inversion Principle. By making User interact with the abstract Chat class, adding new featuresβlike 'Announcement Channels' or 'Voice Rooms'βrequires absolutely zero changes to the User entity. Furthermore, routing friend requests through UserService ensures that the two-way binding of a friendship (A adds B, B adds A) happens synchronously in one controlled environment.
βοΈ Trade-offs & Limitations
| Decision | Pros | Cons / Limitations |
|---|---|---|
| In-Memory Dictionaries | Extremely fast \(O(1)\) lookups for users, friends, and chats. | Data is completely wiped on server restart; cannot scale horizontally across multiple machines. |
| Two-way Friendship Binding | Instant \(O(1)\) check to see if two users are friends. | Requires transactional safety (if storing A in B's list succeeds, but B in A's list fails, the database is corrupted). |
| Appending Messages to a List | Simple to implement for a local prototype. | In production, an unbounded list of millions of messages will cause catastrophic memory exhaustion (Out of Memory Error). |
π€ Interview Toolkit
- Scale Probe: "How do you fetch the last 50 messages of a Group Chat with 10 million total messages without crashing the server?" -> (Do not load the
messageslist into memory. Implement Pagination or Cursor-based fetching, querying the database withWHERE chat_id = X AND message_id < {cursor} ORDER BY timestamp DESC LIMIT 50). - Concurrency Probe: "What happens if two users click 'Accept' on the same friend request at the exact same millisecond?" -> (Implement idempotency and optimistic locking. The
RequestStatusshould only update if the DB readscurrent_status == UNREAD. If it's alreadyACCEPTED, the second thread's transaction is ignored). - System Design Pivot: "If we move this LLD to a distributed microservice architecture, how do users actually receive new messages in real-time?" -> (Clients maintain persistent WebSockets connected to an API Gateway. When a message is sent, a Pub/Sub message broker like Kafka routes the payload to the specific server node holding the recipient's active WebSocket).
π Related Challenges
- Socket Chat App β For the low-level infrastructure and networking side of real-time bidirectional communication.
- Instagram Feed β For handling complex User-to-User following logic and generating aggregated feeds.