Design: Real-Time Chat Quiz

ADVANCED › System Design

While the chat screen is in the foreground, which transport best fits low-latency, bidirectional messaging?

Answer: A persistent WebSocket connection

WebSocket provides a single full-duplex connection with minimal per-message overhead. SSE is server-to-client only, polling wastes radio and battery, and FCM is throttled/best-effort, not a real-time pipe.

The app is backgrounded and the OS has closed its socket. How should new messages reach the user?

Answer: Send a high-priority FCM message to wake the app or post a notification

Doze and App Standby suspend background sockets and processes, so high-priority FCM is the supported wake path. A permanent foreground service is abusive and polling adds latency and battery cost.

To guarantee a consistent message order across all participants, what should be authoritative?

Answer: A server-assigned sequence number or server timestamp

Client clocks drift and can be set incorrectly, so they can't define a global order. The server assigns the canonical sequence/timestamp and clients reorder to match it.

An optimistically-shown message is retried after a network drop. How do you prevent duplicate messages server-side?

Answer: Send a stable client-generated message id as an idempotency key

A stable clientMessageId lets the server recognize and dedupe replays of the same logical message. The other options either drop messages or actively create duplicates.

Which approach paginates chat history reliably as new messages keep arriving?

Answer: Cursor/keyset pagination using a message id or time boundary

Cursor/keyset pagination anchors to a stable boundary, so inserts don't shift pages. OFFSET drifts (skips/duplicates rows) and degrades for deep pages.

Following Android's recommended architecture, what should be the single source of truth for the displayed messages?

Answer: The local Room database, observed through Flow

Offline-first design makes the local DB the SSOT; network and socket events are written into it and the UI observes it reactively, so it renders the same online or offline.

What is the most battery- and bandwidth-friendly way to handle typing indicators and read receipts?

Answer: Debounce/throttle them, batch updates, and auto-expire typing

These are high-frequency, low-value signals, so throttling and batching minimizes radio wakeups and battery drain while still feeling live. The other options multiply network and connection overhead.

TCP can leave a WebSocket half-open (silently dead) after the radio drops. How do you detect that quickly at the app layer?

Answer: Send app-level ping/pong heartbeats; close on a missed pong timeout

OS TCP keepalive is slow and unreliable on mobile, so an app-level ping/pong with a pong-timeout is the standard way to notice a half-open connection promptly and trigger reconnect.

When many clients lose connectivity at once (e.g. after a server blip), how should each app schedule its WebSocket reconnect attempts?

Answer: Use exponential backoff with randomized jitter

Exponential backoff caps reconnect frequency and the random jitter spreads attempts out in time, preventing a synchronized thundering-herd that would overwhelm the server on recovery.

You want WorkManager to flush the persisted offline send queue once the device regains connectivity. Which setup fits best?

Answer: A unique expedited one-time work request constrained to connected network

A constrained one-time worker runs only when the network is available and uniqueness avoids piling up duplicate flush jobs; a permanent foreground service is abusive and an unconstrained minute timer wastes battery.

The client receives messages tagged with server sequence numbers and sees the sequence jump from 100 straight to 103. What is the correct response?

Answer: Detect the 101–102 gap and request a targeted backfill for it

Monotonic server sequence numbers let the client spot gaps and fetch exactly the missing range, restoring correct order and completeness without an expensive full reload.

After a reconnect, the same message can arrive both over the socket and via an FCM-triggered fetch. How do you keep it from appearing twice in the UI?

Answer: Upsert into Room by server message id so the second write no-ops

An idempotent upsert keyed by the server message id collapses redundant deliveries into one row, so duplicate arrivals from different paths can't produce duplicate UI items.

How should large image or video attachments be transmitted in a chat message?

Answer: Upload the file to object storage, then send a small message with its URL

Decoupling the binary upload (resumable HTTP to object storage) from the small text/control message keeps the real-time channel lean, supports retries/progress, and avoids FCM's ~4KB payload limit.

You want infinite-scroll history that is fetched from the network but always rendered from Room as the source of truth. Which Paging 3 component bridges the two?

Answer: A RemoteMediator filling Room from the network for PagingSource to read

RemoteMediator handles boundary-triggered network loads and writes results into Room, while Room's PagingSource feeds the UI, giving an offline-first paged list with the database as the single source of truth.

Back to Design: Real-Time Chat