The Question
FE DesignReal-time Collaborative Whiteboard System Design
Design a high-performance, real-time collaborative whiteboard application similar to Miro or FigJam. The system must support multi-user drawing, text editing, and state synchronization with minimal latency and bandwidth. Detail your approach to handling concurrency conflicts, optimizing 60FPS canvas rendering for thousands of elements, and managing distributed undo/redo history. Address how the system handles intermittent network connectivity and ensures state consistency across diverse clients.
HTML5 Canvas
WebSockets
CRDT
Yjs
TypeScript
Protocol Buffers
React
Questions & Insights
Clarifying Questions
Scale & Concurrency: What is the target number of concurrent users per board?
Assumption: We are targeting 10–20 active collaborators per board for the MVP.
Latency Requirements: Is there a specific latency threshold for "real-time" feel?
Assumption: Sub-100ms glass-to-glass latency is required for a smooth collaborative experience.
Persistence: Do boards need to be saved permanently or are they ephemeral?
Assumption: Boards are persistent; users can return to their work via a unique URL.
Content Complexity: Should we support image uploads or just vector-based drawing/text?
Assumption: MVP focuses on vector shapes (lines, rectangles, circles) and text to minimize bandwidth and complexity.
Offline Support: Is full offline editing required, or just graceful reconnection?
Assumption: Graceful reconnection with state catch-up is sufficient for MVP.
Crash Strategy
Core Bottleneck: Resolving state conflicts across multiple users while maintaining 60FPS rendering performance.
Strategy:
How do we ensure all users see the same state without a central "locking" mechanism? Use Conflict-free Replicated Data Types (CRDTs) to handle merging operations.
How do we render thousands of elements efficiently? Use HTML5 Canvas for the drawing surface rather than SVG to keep the DOM lean and rendering fast.
How do we minimize bandwidth? Send incremental binary diffs (using Yjs or Automerge) over WebSockets instead of full state snapshots.
How do we handle Undo/Redo in a shared environment? Implement a Command Pattern that interacts with the CRDT's distributed history.
Elite Bonus Points
Binary Serialization: Use Protocol Buffers or MessagePack for WebSocket messages to reduce payload size by up to 60% compared to JSON.
Layered Canvas Rendering: Use a "Multi-canvas" approach: one layer for static elements (cached), one for active drawing (low latency), and one for UI overlays (cursors/selection boxes).
Snapshotting: Periodically save a compressed state snapshot to the database to allow new joiners to "fast-forward" without replaying thousands of individual operations.
Design Breakdown
Requirements
Functional Requirements:
Freehand drawing and basic geometric shapes.
Real-time text editing within shapes or as standalone blocks.
Global Undo/Redo (user-specific or board-specific).
Multi-user presence (visible cursors and name tags).
Persistence (automatic saving).
Non-Functional Requirements:
Performance: Interaction latency < 50ms; Rendering at 60FPS.
Scalability: Support boards with 5,000+ elements without lag.
Bandwidth Efficiency: Minimal data transfer for "idle" users; delta-only updates for active ones.
Reliability: Automatic reconnection and state synchronization.
Design Summary
Concise Summary: A client-heavy SPA utilizing an HTML5 Canvas engine backed by a CRDT state management library (Yjs) to synchronize vector operations over WebSockets.
Major Components:
Whiteboard Engine: Manages the Canvas context, coordinate transformations (pan/zoom), and primitive rendering.
CRDT Manager: Orchestrates the shared data structure, ensuring eventual consistency across all clients.
Presence Manager: Handles ephemeral data like peer cursor positions and active selections.
Command Registry: Tracks local operations for Undo/Redo functionality and maps them to CRDT actions.
CUJ Walkthrough:
Drawing: User moves mouse -> Engine updates "Active Layer" canvas -> CRDT Manager broadcasts "path-add" delta -> Peers receive delta and update their local CRDT and Re-render.
Undo: User hits Ctrl+Z -> Command Registry identifies last local action -> CRDT deletes the specific element ID -> Change propagates to all clients.
Simplicity Audit: By using a mature CRDT library (Yjs), we avoid the nightmare of writing a custom Operational Transformation (OT) engine. Using Canvas instead of SVG avoids DOM bloat issues.
Architecture Decision Rationale:
CRDT over OT: CRDTs are easier to implement in a peer-to-peer or decentralized-friendly way and handle concurrent inserts/deletes more robustly than OT without a heavy central server.
Canvas over SVG: For a whiteboard where thousands of paths might exist, SVG nodes would slow down the browser's style calculation and layout phases. Canvas offers direct pixel control and better performance for "infinite" boards.
System Diagram
Architecture Deep Dive
Presentation Layer
Component Hierarchy: The
AppShell handles global navigation and auth. BoardLayout provides the workspace grid. WhiteboardEngine is the core container managing the <canvas> elements and input listeners (Pointer Events API for stylus/touch support).Interaction Layer: Uses the Pointer Events API to normalize mouse, touch, and stylus inputs. Input validation ensures shapes aren't drawn with negative dimensions.
Rendering Layer: Implements a RequestAnimationFrame (rAF) loop. To optimize, we use a "dirty-rect" or full-clear strategy. For the MVP, a dual-canvas approach is best:
Static Canvas: Renders all committed shapes from the CRDT. Only redraws when the shared state changes.
Active Canvas: Renders the "in-progress" shape the user is currently drawing.
UI Frameworks: React or Vue for the UI controls (Toolbar, Presence), but the Whiteboard Engine is a framework-agnostic Class to avoid React re-render overhead during high-frequency mouse moves.
Application Layer
Data Fetching Layer: Initial board state is fetched via REST/GraphQL (
StateHydrator). Subsequent updates are pushed via WebSockets.State Management Layer: Global state is divided into Shared State (managed by Yjs CRDT) and UI State (Current tool, Zoom level). Yjs handles the "Source of Truth," and the UI subscribes to Yjs events to trigger re-renders.
Routing & Navigation: URL contains the
boardId (e.g., ). Route guards ensure the user has permission to join the board.Domain Layer
Business Rules: Shapes have immutable IDs. A "Delete" operation is a tombstone in the CRDT to ensure consistency. Validation prevents "out of bounds" shapes if the board has defined limits.
Entities / Models:
Shape (id, type, points, style), Text (id, content, position), User (id, name, color, cursorPosition).Inter-layer Contracts: The
SyncCoordinator acts as the bridge, translating raw Socket messages into CRDT operations and notifying the WhiteboardEngine.Infrastructure Layer
API / Network: WebSockets (Socket.io) for real-time bi-directional sync. All drawing data is serialized into Uint8Array (binary) by the CRDT library before being sent over the wire, significantly reducing bandwidth compared to JSON strings.
Storage:
LocalStorage is used to cache the last known state of the board to enable instant loading on refresh (Optimistic UI/Hydration).Wrap Up
Wrap-up
Evaluation: The design meets the MVP requirements by focusing on Canvas for performance and CRDTs for sync.
Trade-offs: Canvas makes accessibility (screen readers) harder than SVG. To mitigate, we maintain a hidden "Accessibility Tree" in the DOM that mirrors the whiteboard elements.
Scalability: As the board grows, we would implement Canvas Virtualization (only rendering elements within the current viewport) and Quad-trees for efficient hit-testing (finding which shape was clicked).