Collaborative Whiteboard Low-Level Design: Shape Synchronization, Delta Compression, and Canvas Rendering

Data Model: Shapes

A whiteboard is a collection of shapes. Each shape is a flat record:

{
  id: "uuid",
  type: "rect" | "circle" | "text" | "path" | "arrow",
  x: 100, y: 200,
  width: 300, height: 150,
  style: { fill: "#fff", stroke: "#000", strokeWidth: 2 },
  z_index: 42,
  created_by: "user_id",
  created_at: "2024-01-01T00:00:00Z"
}

Shapes are the unit of synchronization. Text content, path points (for freehand drawing), and arrow endpoints are stored as nested properties within the shape record.

Shape CRDT: Last-Write-Wins per Property

Shape conflicts are resolved with last-write-wins (LWW) per property: each property of a shape carries its own timestamp. When two clients update the same shape concurrently — one moves it (updates x, y) and one resizes it (updates width, height) — both updates are applied, with each property independently won by whichever write has the higher timestamp. This avoids the full-document text CRDT complexity for a shape-based model. Deletes are permanent tombstones: a deleted shape ID can never be reused, and the tombstone is stored indefinitely to reject stale updates to that shape.

WebSocket Delta Sync

Clients send deltas, not full shape state:

{op: "add", shape: {...}}
{op: "update", shape_id: "uuid", changes: {x: 100, y: 200}}
{op: "delete", shape_id: "uuid"}

The server applies the delta, increments a global whiteboard version, and fans the delta out to all other connected clients in the room. Each delta includes the client's session ID so the originating client can suppress its own echo.

Optimistic Local Rendering

Clients apply shape changes locally before the server acknowledges them. This makes the UI feel instant. If the server rejects an operation (e.g., permission error), the client reverts the local change and shows an error. For property conflicts, the server sends the authoritative resolved state back to all clients including the originator, allowing clients to correct their local state if it diverged.

Delta Compression for Drag Operations

A user dragging a shape generates dozens of position updates per second. Broadcasting each intermediate position would saturate the WebSocket. Two strategies:

  • Client-side throttle: send cursor-drag updates at 30fps maximum during drag; send the final position on mouseup
  • Server-side coalescing: for the same shape_id, the server can drop intermediate position updates if a newer one arrives before the previous was broadcast

For real-time cursor positions (not shape moves), a separate low-priority channel broadcasts at 10fps with no persistence — cursor positions are never stored.

Conflict Resolution

Concurrent updates to the same shape property are resolved by timestamp. The server applies updates in arrival order and computes the final winning state per property. It then broadcasts the resolved shape state to all clients, including any client whose update lost. Clients must accept and apply server-authoritative corrections to their local state — they cannot reject server updates.

Infinite Canvas and Viewport Rendering

A whiteboard can contain thousands of shapes. Rendering all of them is unnecessary and slow. The client maintains a viewport — the visible rectangle of the canvas — and only renders shapes that intersect it plus a buffer zone. Spatial indexing uses a quadtree: on each viewport change, query the quadtree for shapes in the new viewport. The quadtree is rebuilt client-side when shapes are added or moved. For initial load, the server sends only shapes within the initial viewport; remaining shapes are fetched lazily as the user pans.

Z-Index Management

“Bring to front” assigns a z_index higher than the current maximum. “Send to back” assigns a z_index lower than the current minimum. Fractional z-indices (e.g., inserting between two shapes) are handled by floating-point values. Periodic reindexing (renumbering all z_index values as integers 1..N in sorted order) prevents floating-point precision exhaustion.

Export Pipeline

Three export formats are supported:

  • SVG: serialize each shape to its SVG equivalent (rect, circle, path elements); assemble into a root SVG element with the full canvas bounding box
  • PDF: render the SVG in headless Chromium using Puppeteer; print to PDF
  • PNG: rasterize the SVG at a specified DPI using a server-side Canvas renderer (Sharp or canvas npm package)

Large exports are queued as background jobs and delivered via a signed download URL when complete.

See also: Atlassian Interview Guide

See also: Meta Interview Guide 2026: Facebook, Instagram, WhatsApp Engineering

See also: Netflix Interview Guide 2026: Streaming Architecture, Recommendation Systems, and Engineering Excellence

Scroll to Top