Real-Time Chat System
System design for chat with typing indicators, read receipts, and presence
Design a real-time chat application (Slack, WhatsApp Web) with typing indicators, read receipts, and presence.
R - Requirements
Key Questions:
- Message types? (text, images, files, emoji)
- Typing indicators? (yes, show when typing)
- Read receipts? (yes, show read status)
- Presence? (online, offline, away)
- Group chats? (yes, multiple participants)
- Message history? (scroll to load older)
Common Answers:
- Text, images, files, emoji
- Typing indicators
- Read receipts (single/double check)
- Presence status
- Group and 1-on-1 chats
Talking Points:
- Real-time chat - WebSocket connection, message ordering, conflict resolution
- Message syncing - Offline queue, background sync, message deduplication
- Messages list - Virtualized reverse scroll, message grouping, infinite scroll
- Chat list - Unread counts, last message preview, presence indicators
A - Architecture
Message Flow:
System Architecture:
Architecture Decisions:
- WebSocket for real-time (bidirectional, low latency)
- Message ordering (timestamp + sequence numbers for conflict resolution)
- Conflict resolution (last-write-wins or CRDT for collaborative editing)
- Offline support (queue messages in IndexedDB, sync when online)
- Normalized store (messages by chat ID + message ID for O(1) lookup)
Relevant Content:
- Chat Architecture - Complete guide
- WebSocket Management - Connection handling
- Presence System - User presence
D - Data Model
Talking Points:
- Message syncing - Offline queue (IndexedDB), background sync, message deduplication
- Messages list - Virtualized reverse scroll, message grouping by date/user
State Management:
- Messages (normalized by chat ID, message ID)
- Typing indicators (per chat, per user)
- Read receipts (per message, per user)
- Presence (per user)
- Message queue (offline messages)
Implementation Example - Message Deduplication:
// Normalized message store
const messages: Record<string, Record<string, Message>> = {};
// messages[chatId][messageId] = message
function addMessage(chatId: string, message: Message) {
// Check if message already exists (deduplication)
if (messages[chatId]?.[message.id]) {
return; // Already have this message
}
// Add to normalized store
if (!messages[chatId]) {
messages[chatId] = {};
}
messages[chatId][message.id] = message;
// Update UI
updateChatUI(chatId, Object.values(messages[chatId]));
}
// Typing indicator throttling
let typingTimeout: NodeJS.Timeout;
function onTyping(chatId: string) {
// Clear previous timeout
clearTimeout(typingTimeout);
// Send typing indicator
sendTypingIndicator(chatId);
// Stop typing after 2 seconds of no input
typingTimeout = setTimeout(() => {
stopTypingIndicator(chatId);
}, 2000);
}Caching:
- Message cache (recent messages, paginated)
- Offline queue (IndexedDB for unsent messages)
- Presence cache (last seen, status)
Relevant Content:
- Caching Patterns - Message cache
- Persistence Strategies - Offline queue
- Offline-First Architecture - Offline support
I - Interface
Talking Points:
- Messages list - Virtualized reverse scroll, message grouping, infinite scroll to load older
- Chat list - Unread counts, last message preview, presence indicators, sorting
Features:
- Message list (virtualized, reverse scroll, grouped by date/user)
- Chat list (unread counts, last message, presence)
- Input field (emoji picker, file upload)
- Typing indicator ("User is typing...")
- Read receipts (single/double check, timestamps)
- Presence indicators (online, offline, away)
- Message status (sending, sent, delivered, read)
- Scroll to bottom button
Accessibility:
- Keyboard navigation
- Screen reader support (announce new messages)
- Focus management (focus input after send)
- ARIA live regions (typing, presence)
Relevant Content:
- ARIA Live Regions - Announcements
- Focus Management - Input focus
- Virtualized List - Message list
O - Optimizations
Performance:
- Virtual scrolling (for long message history)
- Message deduplication (avoid duplicate messages)
- Image optimization (thumbnails, lazy load)
- Code splitting (emoji picker, file upload lazy load)
Real-Time:
- WebSocket connection pooling (reuse connections)
- Message batching (batch multiple messages)
- Connection resilience (reconnect, queue messages)
- Throttle typing indicators (don't send on every keystroke)
Offline:
- Message queue (store unsent messages)
- Background sync (send when online)
Relevant Content:
- WebSocket Management - Connection handling
- Connection Resilience - Reconnect logic
- Background Sync - Offline sync
- Optimistic Updates - Instant messages
Implementation Checklist
- WebSocket connection
- Message list (virtualized, reverse scroll)
- Typing indicators (throttled)
- Read receipts (single/double check)
- Presence system (online, offline, away)
- Message ordering (timestamp, sequence)
- Offline queue (IndexedDB)
- Background sync (send when online)
- Image optimization (thumbnails)
- Accessibility (keyboard, screen readers)
Common Pitfalls
❌ No message ordering → Messages appear out of order
✅ Use timestamps + sequence numbers, handle conflicts
❌ Typing on every keystroke → Too many messages
✅ Throttle typing indicators (1-2s)
❌ No offline support → Lost messages
✅ Queue messages in IndexedDB, sync when online
❌ Poor connection handling → Lost messages, disconnects
✅ Connection resilience, reconnect logic, message queue