#Introduction
You are designing collaborative cursors. Every user moves their mouse, selects text, scrolls, pauses, disconnects, reconnects, and sometimes closes the laptop without sending a clean goodbye.
If you store every cursor movement in the main database, the database becomes a write sink for data that is useless seconds later.
Presence is ephemeral state. It matters right now. It usually should not be durable.
This article is about online status, cursors, selections, typing indicators, and other high-churn state that shows up in chat, collaborative editors, games, and live dashboards.
#Durable State vs Ephemeral State
Durable state must survive restarts:
- document text
- chat messages
- file metadata
- account balances
Ephemeral state can disappear and rebuild:
- online status
- cursor position
- typing indicator
- selected text range
- "currently viewing this page"
The storage choices should be different.
| State | Storage | Why |
|---|---|---|
| Document operation | database or operation log | must be replayed |
| Chat message | message store | users expect history |
| Cursor position | Redis or memory | stale within seconds |
| Online status | Redis TTL | rebuilds on reconnect |
Confusing these two categories is one of the easiest ways to overload the wrong database.
#Heartbeats and TTLs
Presence usually starts with a heartbeat.
1. User opens WebSocket connection.
2. Gateway marks user online with TTL 60 seconds.
3. Client sends heartbeat every 30 seconds.
4. Gateway refreshes TTL.
5. If the client vanishes, TTL expires and user becomes offline.
Redis fits this pattern:
SET presence:user_123 online EX 60
There is no cleanup job. If the browser crashes, the key expires.
For a collaborative editor, presence may be scoped to a document:
doc:123:presence:user_456 -> { cursor, selection, color, lastSeen }
The important part is expiry. Presence without TTL becomes stale presence.
#Cursor and Selection Updates
Cursors move much faster than users type.
Do not broadcast every raw mouse event. Throttle or coalesce updates:
Client mousemove events: 120/sec
Sent cursor updates: 10/sec
Rendered remotely: smooth enough
Cursor payloads should be small:
{
"documentId": "doc_123",
"userId": "user_456",
"cursor": { "line": 12, "column": 8 },
"selection": { "start": 120, "end": 145 },
"version": 482
}
The version matters. If cursor positions are based on document version 482, a client at version 480 may need to transform or delay rendering that cursor.
For text editors, cursor state often follows the same operation transform rules as edits, but with lower durability requirements.
#Scaling Presence Fan-Out
Presence has a fan-out problem.
When one user moves their cursor in a document with 100 viewers, 99 clients may need an update. When a celebrity comes online in a social app, millions of followers do not need an immediate push.
The right fan-out depends on the scope:
- small document room: push cursor updates to active viewers
- group chat: push typing indicators to current participants
- global online status: lazy load when a user opens a chat or profile
Keep the scope tight. Presence should usually be per room, per document, or per visible surface. Global presence broadcasts are expensive and often unnecessary.
For cross-server delivery, use the same coordination patterns as WebSockets: pub/sub, connection registry, or room ownership.
#Common Interview Mistakes
Mistake 1: Storing presence in SQL.
SQL is the wrong tool for high-churn state that expires quickly.
Mistake 2: Forgetting crashed clients.
Users do not always disconnect cleanly. TTL handles that failure mode.
Mistake 3: Broadcasting every cursor movement.
Throttle, coalesce, and only send meaningful state.
Mistake 4: Treating presence as global.
Most presence is only useful to users looking at the same document, room, or chat.
Mistake 5: Ignoring document versions.
Cursor positions can point to the wrong text if clients are on different versions.
#Summary: What to Remember
Presence is high-churn ephemeral state.
Use heartbeats and TTLs for online status. Store cursor and selection state outside the durable document database. Throttle updates. Scope fan-out to active rooms. Tie cursor positions to document versions when text can change underneath them.
In interviews, say the key sentence clearly: durable content goes to the operation log or database; ephemeral presence goes to Redis, memory, or another TTL-backed store.