Design a Messaging App
Concepts Utilized: WebSocket Connection, Local Database (SQLite/Realm), Offline Queue, Push Notifications, End-to-End Encryption, Message Syncing, Media Compression, Read Receipts, Background Service
This document covers the mobile architecture for a messaging application with real-time delivery, offline support, and media sharing.
Requirements
Functional Requirements
| Feature | Description |
|---|---|
| Text messaging | Send and receive text messages |
| Conversations | One-on-one and group conversations |
| Media sharing | Images, videos, and file attachments |
| Message status | Sent, delivered, and read indicators |
| Push notifications | Alerts for new messages |
| Offline support | Queue messages when offline, sync when connected |
| Search | Full-text search across message history |
Non-Functional Requirements
| Requirement | Target |
|---|---|
| Message delivery latency | Under 500ms |
| Offline capability | Full read/write functionality |
| Battery usage | Minimal background consumption |
| Storage efficiency | Optimized for limited device storage |
Architecture Overview
Core Components
1. Local Database Schema
Conversations table: Stores conversation metadata including ID, type (direct or group), name, avatar URL, last message reference for sorting, unread count, muted status, and timestamps.
Messages table: Stores message data including ID, conversation reference, sender, content, content type (text, image, video, file), delivery status (pending, sent, delivered, read, failed), media URLs (remote and local), reply reference, and various timestamps for status tracking.
Outbox table: Holds pending messages for offline queue processing with message reference, serialized payload, retry count, and creation timestamp.
Indexes: Create indexes on messages by conversation and timestamp for chat history queries, by status for pending message processing, and on conversations by last message time for sorting the conversation list.
2. Message Flow
Sending a Message
Receiving a Message
3. Message Status State Machine
4. Offline Support
Message Queue Implementation
Enqueue operation:
- Save message to local database with pending status
- Add to outbox table for queue processing
- Attempt immediate send if network is available
Send pending messages:
- Retrieve all outbox entries
- For each entry, attempt network send
- On success: remove from outbox, update message status to sent
- On failure: increment retry count; mark as failed if max retries exceeded
Network restoration: When connectivity returns, trigger processing of all pending messages in the queue.
Sync Strategies
| Scenario | Strategy |
|---|---|
| App opens after being closed | Fetch messages since last sync timestamp |
| Connection restored after offline | Send queued messages, fetch new messages |
| Background fetch (iOS) | Sync recent messages, update badges |
| Push notification tap | Fetch specific conversation |
Real-Time Communication
WebSocket Connection
Connection management:
- On connect: Reset reconnect counter, start heartbeat timer, subscribe to message channels
- On message: Parse and route to message handler
- On disconnect: Stop heartbeat, schedule reconnection
Reconnection strategy: Use exponential backoff with a maximum delay (such as 30 seconds). Track reconnect attempts to calculate delay. After each failed attempt, wait longer before retrying.
Heartbeat: Send periodic ping messages (every 30 seconds) to maintain the connection and detect disconnections promptly.
Push Notifications
Push notifications deliver messages when the WebSocket connection is inactive.
Implementation approach:
- Handle remote notification in the app delegate method
- Extract message data from the notification payload
- Parse and save the message to the local database
- Query total unread count and update the app badge number
- Call the completion handler with the appropriate result
Media Handling
Image Upload Flow
Image Sizing
| Size | Dimensions | Use Case |
|---|---|---|
| Thumbnail | 100x100 | Conversation list preview |
| Preview | 300x300 | Chat bubble preview |
| Full | 1200xAuto | Full screen view |
| Original | As-is | Download option |
Media Caching
Two-tier caching strategy:
- Create cache key combining URL and size
- Check memory cache first (NSCache); return if found
- Check disk cache; if found, promote to memory cache and return
- If not cached, return nil (caller should download)
Cache writes: Store in both memory and disk caches. Memory cache provides fast access for current session; disk cache persists across app restarts.
Performance Optimizations
1. Message List Virtualization
Render only visible messages using lazy loading.
UIKit approach: Use UITableViewDiffableDataSource to manage message display. Create snapshots with updated messages and apply with automatic animation handling.
SwiftUI approach: Use LazyVStack inside ScrollView. Only visible message bubbles are rendered; others are created on demand as the user scrolls.
2. Database Query Optimization
Paginated loading: Query messages with a limit and cursor (timestamp). Select from messages where conversation matches and timestamp is before the cursor, ordered by timestamp descending, with a fixed limit. This enables loading older messages in chunks as the user scrolls up.
3. Batch Operations
Inefficient approach: Looping through messages and updating status individually triggers multiple database writes and notifications.
Efficient approach: Execute a single SQL UPDATE statement with a WHERE clause matching all relevant messages. This performs one database operation regardless of message count.
4. Background Processing
Implementation approach: Dispatch CPU-intensive operations (like image compression) to a background queue. When complete, dispatch back to the main queue for UI updates or network operations.
Battery Optimization
| Technique | Implementation |
|---|---|
| Batch network requests | Combine multiple operations into single requests |
| Smart sync intervals | Increase frequency when app is active |
| Efficient WebSocket | Single connection shared across conversations |
| Background fetch limits | Respect iOS/Android background execution quotas |
| Image compression | Reduce upload sizes to minimize transmission time |
Security
End-to-End Encryption (E2EE)
Key Storage
| Platform | Secure Storage API |
|---|---|
| iOS | Keychain Services |
| Android | Android Keystore |
Summary
| Decision | Options | Recommendation |
|---|---|---|
| Data persistence | Core Data, SQLite, Realm | SQLite with wrapper for flexibility |
| Real-time transport | Polling, WebSocket, SSE | WebSocket for bidirectional communication |
| Offline strategy | Queue & sync | Local-first with outbox pattern |
| Image handling | Upload then send, Inline base64 | Upload first, send URL reference |
| List rendering | UITableView, SwiftUI List | Diffable data source or LazyVStack |