Designing Chat & Messaging Apps
📖 Concept
Designing a chat system is one of the most common mobile system design questions. It tests your knowledge of real-time communication, message ordering, delivery guarantees, and offline handling.
Key components:
- Message delivery pipeline: Client → Server → Recipient (via push or WebSocket)
- Message states: Sent → Delivered → Read (WhatsApp-style ticks)
- Persistence: Local SQLite/Room for message history
- Real-time: WebSocket for active sessions, FCM for background delivery
- Media: Separate upload pipeline for images/videos
Architecture:
┌──────────────────────────────────┐
│ Chat UI (LazyColumn) │
│ ↕ │
│ ChatViewModel │
│ ↕ │
│ MessageRepository │
│ ↕ ↕ │
│ Room DB ConnectionManager │
│ (history) ↕ ↕ │
│ WebSocket FCM │
│ (online) (offline)│
└──────────────────────────────────┘
Message ordering challenges:
- Network delays can cause messages to arrive out of order
- Solution: Server-assigned sequential IDs + client-side sorting
- Optimistic: show sent messages immediately with local timestamp, reconcile with server order later
Delivery guarantees:
- At-most-once: Fire and forget. Messages may be lost.
- At-least-once: Retry until acknowledged. May deliver duplicates.
- Exactly-once: Deduplicate using message IDs. Most complex but required for chat.
💻 Code Example
1// Chat system architecture — key components23// Message entity with delivery state4@Entity(tableName = "messages")5data class MessageEntity(6 @PrimaryKey val id: String = UUID.randomUUID().toString(),7 val conversationId: String,8 val senderId: String,9 val content: String,10 val type: String = "TEXT", // TEXT, IMAGE, VIDEO11 val localTimestamp: Long = System.currentTimeMillis(),12 val serverTimestamp: Long? = null,13 val deliveryState: String = "SENDING", // SENDING, SENT, DELIVERED, READ, FAILED14 val mediaUrl: String? = null,15 val localMediaPath: String? = null16)1718// WebSocket connection manager19class ChatConnectionManager @Inject constructor(20 private val okHttpClient: OkHttpClient,21 private val messageDao: MessageDao22) {23 private var webSocket: WebSocket? = null24 private val _incomingMessages = MutableSharedFlow<MessageEntity>()25 val incomingMessages: SharedFlow<MessageEntity> = _incomingMessages2627 fun connect(authToken: String) {28 val request = Request.Builder()29 .url("wss://chat.example.com/ws")30 .addHeader("Authorization", "Bearer $authToken")31 .build()3233 webSocket = okHttpClient.newWebSocket(request, object : WebSocketListener() {34 override fun onMessage(ws: WebSocket, text: String) {35 val message = Json.decodeFromString<MessageEntity>(text)36 CoroutineScope(Dispatchers.IO).launch {37 messageDao.upsert(message)38 _incomingMessages.emit(message)39 }40 }4142 override fun onFailure(ws: WebSocket, t: Throwable, response: Response?) {43 // Reconnect with exponential backoff44 scheduleReconnect()45 }46 })47 }4849 fun sendMessage(message: MessageEntity) {50 val json = Json.encodeToString(message)51 val sent = webSocket?.send(json) ?: false52 if (!sent) {53 // Queue for retry when connection restored54 CoroutineScope(Dispatchers.IO).launch {55 messageDao.upsert(message.copy(deliveryState = "FAILED"))56 }57 }58 }59}6061// Chat Repository — offline-first messaging62class ChatRepository @Inject constructor(63 private val messageDao: MessageDao,64 private val connectionManager: ChatConnectionManager,65 private val mediaUploader: MediaUploader66) {67 fun getMessages(conversationId: String): Flow<List<MessageEntity>> =68 messageDao.getByConversation(conversationId)6970 suspend fun sendTextMessage(conversationId: String, content: String) {71 val message = MessageEntity(72 conversationId = conversationId,73 senderId = currentUserId,74 content = content,75 deliveryState = "SENDING"76 )77 // Optimistic: save locally first (appears in chat immediately)78 messageDao.upsert(message)79 // Then send via WebSocket80 connectionManager.sendMessage(message)81 }82}
🏋️ Practice Exercise
Practice:
- Design the message delivery pipeline with SENT/DELIVERED/READ states
- Implement WebSocket reconnection with exponential backoff
- Handle message ordering when messages arrive out of order
- Design the media upload pipeline for images in chat
- How would you implement end-to-end encryption for messages?
⚠️ Common Mistakes
Not handling WebSocket disconnection — mobile connections are unreliable, always implement reconnection
Using only WebSocket without FCM fallback — background apps can't maintain WebSocket connections
Not deduplicating messages — at-least-once delivery requires idempotent message handling
Displaying messages by local timestamp instead of server timestamp — causes ordering issues
💼 Interview Questions
🎤 Mock Interview
Mock interview is powered by AI for Designing Chat & Messaging Apps. Login to unlock this feature.