How Would You Design a Real-Time Chat App?
Learn how to design a scalable real-time chat application using WebSockets, pub/sub messaging, presence tracking, and durable storage for interviews.
Expected Interview Answer
A real-time chat app keeps a persistent WebSocket connection per online user, routes messages through a pub/sub layer to the recipient's connection, and stores every message durably so history survives disconnects and app restarts.
Clients open a WebSocket to a connection-handling server, which registers the user's session in a presence store (e.g. Redis) so the system knows which server instance holds that connection. When a message is sent, the API writes it to a durable store (a database sharded by conversation ID) and publishes it on a message broker or pub/sub channel keyed by recipient; whichever server instance holds that recipient's live connection delivers it instantly, while offline recipients get it on next reconnect via a fetch of undelivered messages. Group chats fan out to all members' channels, and features like typing indicators and read receipts ride on the same pub/sub layer as lightweight, non-durable events.
- WebSockets give low-latency bidirectional delivery
- Pub/sub decouples message routing from server instance
- Durable storage guarantees no message loss on disconnect
- Presence store enables horizontal scaling of connection servers
- Same infra supports typing indicators and read receipts
AI Mentor Explanation
A chat app is like a stadium's ball-by-ball radio link between the scorer and the broadcast booth, kept open the whole match so updates arrive the instant a ball is bowled rather than at the end of the over. If the broadcast booth briefly loses signal, the scorer's written record still holds every ball bowled, so the booth catches up completely the moment the link reconnects.
Step-by-Step Explanation
Step 1
Open a persistent connection
Clients establish a WebSocket (or long-poll fallback) to a connection server on login, kept alive with heartbeats.
Step 2
Track presence
Register which server instance holds each user's live connection in a shared presence store like Redis.
Step 3
Persist every message first
Write the message to a durable, conversation-sharded database before attempting real-time delivery.
Step 4
Route via pub/sub
Publish the message on a broker keyed by recipient; the server holding that connection delivers it instantly.
Step 5
Handle offline recipients
On reconnect, clients fetch undelivered messages since their last-seen cursor so nothing is lost.
What Interviewer Expects
- Chooses WebSockets over plain HTTP polling and explains why
- Separates durable storage from real-time delivery
- Explains how presence tracking enables horizontal scaling
- Handles offline users and reconnection without message loss
- Discusses group chat fan-out and typing/read-receipt events
Common Mistakes
- Relying only on in-memory delivery with no durable message store
- Assuming one server can hold every WebSocket connection at scale
- Forgetting to design for offline users and message replay on reconnect
- Treating typing indicators the same as durable chat messages
Best Answer (HR Friendly)
“A chat app keeps a live, always-open connection between your device and the server so messages appear instantly, while also saving every message to a database so nothing is lost if you go offline. When you reconnect, the app simply fetches anything you missed, giving a smooth, real-time experience even on unreliable networks.”
Code Example
const connections = new Map(); // userId -> socket
wss.on('connection', (socket, req) => {
const userId = getUserIdFromRequest(req);
connections.set(userId, socket);
socket.on('message', async (raw) => {
const msg = JSON.parse(raw);
await db.messages.insert({ ...msg, createdAt: Date.now() });
const recipientSocket = connections.get(msg.recipientId);
if (recipientSocket) {
recipientSocket.send(JSON.stringify(msg)); // instant delivery
} // else: recipient fetches it from db on next reconnect
});
socket.on('close', () => connections.delete(userId));
});Follow-up Questions
- How would you scale WebSocket connections across many server instances?
- How do you guarantee message ordering within a conversation?
- How would you implement read receipts without overloading the database?
- How do you handle a user connected from two devices at once?
- How would you support end-to-end encryption in this design?
MCQ Practice
1. Why prefer WebSockets over repeated HTTP polling for chat?
A WebSocket stays open and lets the server push messages instantly instead of the client repeatedly asking for updates.
2. Why does a chat system persist messages before real-time delivery?
Durable storage guarantees no message is lost even if the recipient is offline or a server crashes mid-delivery.
3. What is the role of a presence store like Redis in this design?
Presence tracking lets any server instance know where to route a message for instant delivery across a horizontally scaled fleet.
Flash Cards
Why use pub/sub between servers in a chat system? — It decouples which server receives a message from which server holds the recipient's live connection, enabling horizontal scale.
What happens when a recipient is offline? — The message is stored durably and delivered on reconnect via a fetch of undelivered messages since their last-seen cursor.
Are typing indicators stored durably? — No, they are lightweight, ephemeral pub/sub events that don't need persistence like actual chat messages do.
How is a conversation typically sharded in storage? — By conversation ID, so all messages in one chat live together and scale independently of other conversations.