Design Realtime Collaboration Cursors
Frontend system design for multiplayer cursors — presence, throttling, smoothing, layers, and awareness protocols.
- system-design
- interview
- architecture
- realtime
- collab
Scope the problem
Design multiplayer presence cursors on a shared canvas or document: show other users’ pointers/carets in near realtime.
In scope: client presence channel, throttle/batch, render layer, identity colors, leave/join, performance under many peers.
Out of scope: full CRDT document sync (mention separation), WebRTC mesh topology deep dive unless needed.
Requirements
| Type | Examples |
|---|---|
| Functional | see peers move; names/avatars; hide self |
| Non-functional | < 100ms perceived lag on good networks; 60fps UI |
| Scale | 10–50 concurrent viewers typical; call out 500+ |
Separate concerns
Document state sync (CRDT/OT) ≠ Awareness/presence (ephemeral)
Cursors are awareness — lossy, high-frequency, no need for durable persistence.
Architecture
Pointer events → local cursor state → throttle → WS send
↓
server relay (room)
↓
Other clients → interpolate/render overlay layer
Room id = document id. Auth: signed room token.
Wire protocol (sketch)
type AwarenessMsg =
| {
type: "cursor";
userId: string;
// document-relative coordinates OR index in text model
x: number;
y: number;
ts: number;
selection?: { anchor: number; head: number };
}
| { type: "leave"; userId: string }
| { type: "join"; userId: string; name: string; color: string };
Coordinate space: use document-relative positions (canvas world coords or ProseMirror positions), not raw viewport pixels — otherwise scroll/zoom desyncs peers.
Client modules
AwarenessClient
├── connection (WS/SSE)
├── localPublisher (throttle 30–50ms)
├── remoteStore (Map<userId, RemoteCursor>)
└── CursorOverlay (React/Canvas)
Throttle publish
function createPublisher(send: (m: AwarenessMsg) => void, ms = 33) {
let last: AwarenessMsg | null = null;
let timer: number | null = null;
return (m: AwarenessMsg) => {
last = m;
if (timer != null) return;
timer = window.setTimeout(() => {
timer = null;
if (last) send(last);
}, ms);
};
}
On pointerup / blur send final position. On tab hidden stop sending; on leave send leave.
Rendering
| Approach | Pros | Cons |
|---|---|---|
| DOM nodes per cursor | simple a11y labels | reflow cost |
| Single canvas overlay | fast for many | text a11y harder |
| SVG layer | sharp | similar to DOM |
Smoothing: interpolate between last two remote points with rAF; don’t animate to stale targets after long gaps (jump).
// each frame
remote.displayX += (remote.targetX - remote.displayX) * 0.4;
Presence list
Sidebar avatars bound to same awareness store. Idle timeout: if no packet for 10s, mark away; 60s remove.
Scaling & backend interface
- Pub/sub room channel; don’t durable-log cursor stream
- Binary messages (MessagePack) if JSON CPU-bound
- Server can drop intermediate cursor packets under load (lossy OK)
- Sticky sessions not required if room state is in Redis pubsub
Performance budgets
- Cap rendered cursors (nearest N, or active speakers)
- Avoid React re-render of whole doc tree — isolate overlay store (zustand/external store)
- Passive pointer listeners; batch reads
Privacy & UX
- Opt-out “Share my cursor”
- Color contrast for labels
- Don’t expose emails — display names only
- Follow mode (viewport follows lead user) as extension
Relation to document sync
Cursors may include selection ranges that must map through same document model version. If CRDT not yet synced, selection indices can be wrong — either:
- Attach to stable CRDT relative positions
- Or only show free-floating pointer until sync catches up
Tradeoffs
- WS vs WebRTC datachannel — server relay simpler; WRTC lower latency peer meshes hard
- DOM vs canvas overlay
- High frequency vs battery on mobile — adaptive throttle
- Accuracy vs smoothness
Interview close
Ephemeral awareness channel separate from durable doc sync; document-relative coords; throttle publishes; smooth remote render; lossy server OK; isolate overlay from React doc renders. Extend to selections and follow mode.
Related on this site
- Realtime WebSocket UI Design
- Design Offline-first Notes App
- State Management at Scale
- System Design Interview Framework