How to Design a Chat Presence System
Design a chat presence system: WebSocket heartbeats, TTL-based offline detection, and pub/sub broadcasting at scale.
Expected Interview Answer
A presence system tracks whether each user is online, offline, or away by having clients maintain a persistent connection (WebSocket) with periodic heartbeats to a presence service, which stores current status in a fast in-memory store and pushes updates to interested clients via pub/sub.
Each client opens a WebSocket to a presence server and sends a heartbeat every few seconds; the server marks the user online and refreshes a TTL key in a store like Redis. If no heartbeat arrives before the TTL expires (typically 30-60 seconds after the last one), the server treats the user as offline and publishes a status-changed event. Presence data is sharded across many presence servers behind a connection-aware load balancer, and status changes are broadcast through a pub/sub layer (Redis Pub/Sub or Kafka) to any other servers holding connections for that user's friends or chat participants, who then push the update down their own WebSockets. To handle scale, presence is treated as eventually consistent and batched — client UIs poll or subscribe to presence for only the contacts currently visible, not the whole friend list, to avoid a fan-out explosion.
- Gives near real-time online/offline visibility without polling every client repeatedly
- TTL-based heartbeat expiry cleanly handles ungraceful disconnects (crashed apps, dropped networks)
- Sharding presence servers and using pub/sub lets the system scale horizontally
- Subscribing only to visible contacts avoids a combinatorial fan-out of status updates
AI Mentor Explanation
A presence system is like the electronic scoreboard operator confirming a fielder is still on the pitch by radio check-ins every few minutes instead of staring at every player constantly. As long as check-ins keep arriving, the board shows the fielder as active; if a check-in is missed past the allowed window, the board flips them to off-field. When status changes, the operator broadcasts the update to the dugout and commentary box (subscribers) rather than making everyone individually ask. That heartbeat-and-broadcast pattern is exactly how a chat presence system tracks who is online.
Step-by-Step Explanation
Step 1
Establish persistent connection
Client opens a WebSocket to a presence/chat server on app foreground or login.
Step 2
Send periodic heartbeats
Client pings every few seconds; server refreshes a TTL key for that user in Redis.
Step 3
Detect disconnects via TTL expiry
If no heartbeat arrives before the TTL, the key expires and the user is marked offline.
Step 4
Broadcast status changes
A status-changed event is published via pub/sub to servers holding connections for interested contacts.
What Interviewer Expects
- Explains heartbeat + TTL as the core online/offline detection mechanism
- Names a fast store (Redis) for current status and a pub/sub layer for broadcasting changes
- Addresses fan-out limits by subscribing only to currently visible contacts
- Acknowledges presence is eventually consistent, not strictly real-time exact
Common Mistakes
- Proposing clients poll a REST endpoint repeatedly instead of a persistent connection
- Ignoring ungraceful disconnects (app crash, network drop) and relying only on explicit logout
- Broadcasting every status change to a user's entire friend list regardless of who is watching
- Not discussing how presence servers scale horizontally with sharded connections
Best Answer (HR Friendly)
“A presence system keeps a persistent connection open between your device and the server, and your app quietly pings the server every few seconds to say "I am still here." If those pings stop for too long, the server assumes you went offline and tells your contacts. It is designed to update quickly without constantly polling every user.”
Code Example
const HEARTBEAT_INTERVAL_MS = 10000
const TTL_SECONDS = 30
// Client side
setInterval(() => {
socket.send({ type: "heartbeat", userId })
}, HEARTBEAT_INTERVAL_MS)
// Server side
async function onHeartbeat(userId) {
await redis.set(`presence:${userId}`, "online", { EX: TTL_SECONDS })
await pubsub.publish(`presence-changed:${userId}`, "online")
}
// Redis key expiry notification triggers offline broadcast
redis.on("expired", async (key) => {
if (key.startsWith("presence:")) {
const userId = key.split(":")[1]
await pubsub.publish(`presence-changed:${userId}`, "offline")
}
})Follow-up Questions
- How would you scale presence for a user with millions of followers watching their status?
- How do you distinguish “away” from “offline” in a presence system?
- What happens to presence accuracy during a rolling deployment of presence servers?
- How would you reduce the heartbeat frequency to save battery on mobile without losing accuracy?
MCQ Practice
1. What mechanism most commonly detects that a user has gone offline in a presence system?
Heartbeats refresh a TTL key; when heartbeats stop, the TTL expires and the system infers the user disconnected.
2. Why do presence systems avoid broadcasting every status change to a user's entire friend list?
Only broadcasting to currently visible/subscribed contacts avoids overwhelming the system for high-follower-count users.
3. Which transport is most appropriate for a presence system's client connection?
A persistent connection lets the server push status changes immediately without repeated client polling.
Flash Cards
Core presence detection mechanism? — Client heartbeats refresh a TTL key; expiry implies offline.
Why use pub/sub for presence? — To broadcast status changes to interested subscribers across sharded servers in real time.
Why limit subscriptions to visible contacts? — To avoid a fan-out explosion when broadcasting status changes to huge friend lists.
Is presence strongly or eventually consistent? — Eventually consistent — brief staleness is acceptable in exchange for scale.