A Google Docsβstyle collaborative code editor built with React + Monaco Editor, WebSockets, and MongoDB.
Supports real-time cursor tracking, batched updates, and efficient compaction of operational logs.
- ποΈ Live Code Editing β Multiple users can edit the same file simultaneously.
- π₯ Remote Cursor Tracking β See collaborators' cursors and names in real-time.
- β‘ Low-Latency Updates β
- Cursor positions are emitted instantly.
- Text updates are sent per keystroke for responsiveness.
- Backend updates are batched for efficiency.
- π¦ Operational Log Storage (CRDT-inspired) β Every edit is stored as a payload with position and timestamp.
- π§Ή Automatic Compaction β
- Old operations are periodically merged into snapshots.
- Keeps database size manageable and lookup efficient.
- π Diff/Patch Algorithm β Efficiently applies incoming changes with near O(1) updates.
- Frontend: React, Monaco Editor, TailwindCSS
- Backend: Node.js, Express.js, WebSocket
- Database: MongoDB (Ops log + snapshots)
-
Payload Generation
- Each edit generates a payload:
{ "startIndx": 0, "endIndx": 0, "newLines": ["e"], "startColumn": 0, "endColumn": 0, "deleteCount": 1, "timeStamp": 1694567890000 }
- Each edit generates a payload:
-
Frontend β Backend
- Cursor updates: sent instantly.
- Text updates: emitted live but batched before DB writes.
-
Backend Storage
- Stores operations like a CRDT log.
- Runs compaction every 10 minutes or when a file is opened.
- Operations update in DB takes O(1) in general.
-
Diff/Patch Algorithm
- Applies ops in timestamp order.
- Achieves O(1) for most updates, O(n) worst case during compaction.
- Cursor emission latency: ~30β80 ms
- Text update latency: ~100β200 ms
- DB writes: Batching reduces ops from 1000s β 10s
- Compaction complexity: amortized ~O(1)βO(n), runs in background
- This project implements a custom text buffer inspired by ropes and CRDTs, using a LinkedList + Map hybrid for O(1) line access and update.
- Keystroke operations are batched (coalesced) into larger diffs to reduce overhead.
- π Access control (roles: leader, member).
- π Syntax highlighting with Monacoβs language services.
- π Support for cross-region low-latency replication.
sequenceDiagram
participant UserA as User A (Editor)
participant FE as Frontend
participant WS as WebSocket Server
participant BE as Backend (Express)
participant DB as MongoDB
participant UserB as User B (Editor)
UserA->>FE: Type code / Move cursor
FE->>WS: Emit payload (cursor or text)
WS->>BE: Forward event
BE->>DB: Store operation (batched)
BE-->>WS: Broadcast to other users
WS-->>UserB: Send update
UserB->>FE: Apply diff/patch
sequenceDiagram
participant U as User (Monaco)
participant D as Diff Engine
participant RT as Real-time Server
participant DB as Ops Store
participant P as Peers
U->>D: Local edit (range, text)
D->>D: Compute minimal diff (start,end,newText)
D-->>RT: Patch payload
RT->>DB: Append op (O(1))
RT-->>P: Broadcast patch
P->>P: Apply patch β update editor
sequenceDiagram
participant Worker as Compaction Worker
participant DB as MongoDB
participant Snap as Snapshots
Worker->>DB: load latest snapshot
Worker->>DB: fetch ops after snapshot.version
Worker->>Worker: apply ops in-memory
Worker->>Snap: write new snapshot
Worker->>DB: delete archived ops
sequenceDiagram
participant U as User (Monaco)
participant WS as WebSocket/RT service
participant DB as Ops DB
participant P as Peer(s)
U->>WS: code-change (range, text, ts)
WS->>DB: append op (O(1))
WS-->>P: broadcast remote-change
P->>PeerEditor: applyEdits()
flowchart TD
subgraph YourAlgo["Hybrid Approach (LinkedMapBuffer)"]
C1["LinkedList β Maintains line order (O(1) insert/delete)"]
C2["HashMap β Direct access by lineId (O(1))"]
C3["Ops Batching β Merge keystrokes into single op"]
C4["Diff-Patch β Store transformations instead of whole text"]
C1 --> C2
C2 --> C3
C3 --> C4
end
When working on real-time editors, every keystroke usually triggers a payload thatβs sent to the backend.
This can quickly flood the network with redundant updates β especially when a user types fast or deletes characters continuously.
To tackle this, I implemented an operation coalescing algorithm that intelligently merges multiple nearby operations into a single optimized payload before synchronization.
- Each edit (insert or delete) is captured as an operation with metadata:
startIndexstartColumnendColumndeleteCountnewString
- Before sending updates, operations on the same line or in close proximity are merged using the
mergeOps()function. - Both insertions and deletions are handled gracefully to maintain data integrity and consistent cursor behavior.
- The result: fewer network calls, less load on the database, and a smoother collaborative experience.
| Case | Without Coalescing | With Coalescing |
|---|---|---|
| Typing 24 characters | 24 payloads sent | 4 payloads sent |
| Reduction | β | β83.3% fewer payloads π |
[
{ startColumn: 1, newString: "H" },
{ startColumn: 2, newString: "e" },
{ startColumn: 3, newString: "l" },
{ startColumn: 4, newString: "l" },
{ startColumn: 5, newString: "o" }
][
{ startColumn : 1, newString : "Hello", endColumn : 5 }
]