Multithreaded Chat System
Overview
Design the in-memory core of a chat system. Users join and leave rooms, and a message sent to a room is delivered to every current member. Many users act at the same time from different threads, so the shared state — room membership and message history — must stay consistent under concurrent joins, leaves, and sends.
This question is about the chat domain model and its synchronization, not the networking. The tricky part is broadcasting safely: a send must fan a message out to all members without missing someone who is joining or touching someone who just left, and without holding a lock so long that the whole room stalls.
Interfaces
class ChatServer:
def __init__(self):
self._rooms = {} # room_id -> Room
self._lock = Lock()
def join(self, user, room_id):
"""Add user to a room (creating it if needed)."""
...
def leave(self, user, room_id):
"""Remove user from a room."""
...
def send(self, user, room_id, text):
"""Deliver text to every current member of the room."""
...
def history(self, room_id, limit=50):
"""Return the most recent messages in a room."""
...
class Room:
def __init__(self):
self._members = set()
self._messages = []
self._lock = Lock() # guards this room only
Give each room its own lock so activity in one room never blocks another. A send snapshots the member set under the room lock, then delivers outside the lock, so a slow delivery doesn't freeze joins and leaves.
Inputs and outputs
- Input:
join,leave,send, andhistorycalls from many threads. - Output: every member present at send time receives the message exactly once; membership and history never corrupt under concurrency.
Requirements
- Room membership is correct under concurrent joins and leaves.
- A broadcast reaches all members at the moment of the send; no member is skipped or double-delivered.
- Different rooms operate fully in parallel.
- Don't hold a room's lock while doing slow per-member delivery.
- Message history is appended consistently and reads return a coherent slice.
Examples
s = ChatServer()
s.join("alice", "general")
s.join("bob", "general")
s.send("alice", "general", "hi") # delivered to alice and bob
s.leave("bob", "general")
s.send("alice", "general", "still here?") # delivered to alice only
Think about it
You're iterating a room's members to deliver a message, and someone joins or leaves mid-broadcast.
What breaks if you hold the room lock for the whole delivery? What breaks if you don't hold it at all?
Where's the line between those two?
Follow-ups
One slow client shouldn't stall the room. How would a per-user outbound queue keep backpressure local to that client?
How would you add direct messages and presence (who's online) without bolting on a separate system?
If two people send at once, what ordering does each member see — and can you guarantee they all see the same order?
And more...
FANG Crash Course
Fullset Newest FANG Coding/System Design Questions, Optimal Solutions and Explanations.
Step_1 Matched with a FAANG+ Senior Engineer
Step_2 Fullset FANG Coding/System Design Questions
Step_3 Solutions + step by step Explanation
