Real Time Collaboration: A Web App Engineering Primer
Real-time collaboration in web applications allows multiple users to view and edit the same content simultaneously, with changes from each user instantly reflected in all other users' views. The core technical problems are conflict resolution (two users editing the same field simultaneously), state synchronization (ensuring all clients eventually see the same state), presence (showing who is currently viewing or editing), and latency compensation (showing local changes immediately before the server confirms them). WebSockets are the standard transport for real-time collaboration features.
Written by Yashveer Singh, founder of Yashveer Labs.
What you need to know
- WebSockets are the right transport for collaboration features. HTTP polling is inadequate for the update frequency that makes collaboration feel real-time.
- Do not implement conflict resolution from scratch. Use Yjs (CRDT-based) or ShareDB (OT-based). Both are production-tested and handle the edge cases that manual implementations miss.
- Presence (live cursors, online indicators) requires a heartbeat mechanism. Clients that disconnect without sending a goodbye message must be expired by the server on heartbeat timeout.
- Optimistic updates are required for the feature to feel responsive. Users should see their own changes immediately, before server confirmation.
- Scale testing matters more than unit testing for collaboration features. Bugs in conflict resolution often only appear under concurrent edits from multiple users.
The core argument
Real-time collaboration is one of the features that is easy to add for the simple case and genuinely difficult to implement correctly for the general case. A shared todo list where two users can add items simultaneously is straightforward: append-only operations commute, conflicts are trivially merged, and a simple WebSocket broadcast of operations works. A shared rich text document where two users can edit the same paragraph simultaneously requires a conflict resolution algorithm, and implementing that algorithm incorrectly produces corrupted documents in a small but non-zero fraction of concurrent edit scenarios.
The practical advice for most product teams is: use a library. Yjs is the production-ready CRDT library that powers collaborative features in products like Notion, Linear, and countless others. It handles conflict resolution, undo history, and presence, and has providers for multiple backends (WebSocket, WebRTC, IndexedDB for offline). The engineering work is integration, not algorithm implementation. A team that builds a custom OT algorithm to avoid Yjs's complexity will spend weeks on the algorithm and years fixing the edge cases Yjs has already solved.
The architecture that works for most SaaS collaboration features is: Yjs documents for shared document state, a WebSocket server (Hocuspocus is the standard Yjs WebSocket server) for synchronization, and awareness for presence data. The result is a synchronization layer that handles network partitions, reconnection, offline edits, and conflict resolution without requiring the product team to understand the implementation details.
Common mistakes
- Using a database table as the collaboration store. Collaborative features require a real-time synchronization mechanism, not a polling loop that reads a database table every second. Database polling at the frequency required for responsive collaboration creates significant database load and still produces a 1-second latency minimum. WebSockets with an in-memory or Redis-backed state store is the correct architecture.
- Not handling reconnection. WebSocket connections drop due to network interruptions, sleep/wake cycles, and server restarts. The client must reconnect automatically and resynchronize state after reconnection. Yjs handles this through its provider architecture, but custom WebSocket implementations must explicitly handle reconnection with backoff and state reconciliation.
- Broadcasting all changes to all connected users. In a large collaborative application, not all users need all updates. A user editing document A should not receive updates for document B. Implement room or channel scoping so WebSocket broadcasts are targeted to the relevant connected clients.
- Ignoring the conflict between optimistic updates and authoritative server state. Optimistic updates show local changes immediately. If the server rejects or transforms the operation, the local state must be corrected. Without this reconciliation, the user's local view diverges from the server state silently. Yjs's CRDT approach handles this naturally because the local and remote state always merge deterministically.
- Not testing concurrent edit scenarios. Collaboration bugs are often specific to concurrent edits with specific timing. Manual testing with two browsers is insufficient for coverage. Write integration tests that simulate concurrent operations from multiple clients and verify the final merged state is correct.
Where to start
- Choose the collaboration library before designing the data model. Yjs and ShareDB have different data model constraints. Yjs works with Y.Doc as the top-level data structure containing Y.Map, Y.Array, and Y.Text types. Design the collaborative data model in terms of the library's types before writing application code.
- Deploy Hocuspocus (for Yjs) or ShareDB backend first without the frontend. Test the synchronization backend independently with a command-line client before building the frontend. Confirm that two clients can connect, make edits, and see each other's edits merged correctly.
- Implement presence separately from document synchronization. Yjs Awareness handles presence data (cursor positions, user names, online status) independently from document state. Implement the presence visualization (colored cursors, user avatars in the toolbar) as a separate feature after document synchronization works correctly.
Related reading
Frequently asked
About the author and why it matters
Yashveer Singh wrote this. I run Yashveer Labs out of New Delhi. The work I take on tends to come from founders who have been burned by an agency, a freelancer, or their own ambition. I do not promise miracles. I promise that the system will be online, the code will be readable, and the next engineer who touches it will not curse me. That is rarer than it should be.
Posts that line up with this one.
- Web App and Frontend Development
Loading States, Skeletons, and Optimistic UI
How you handle loading states is one of the most visible indicators of product quality. Here is the decision framework for when to use spinners, skeletons, and optimistic updates, and the common mistakes that make apps feel slow.
- Web App and Frontend Development
Modal Patterns That Do Not Trap Users
Modals are overused, frequently misimplemented, and a common source of user frustration. Here is how to design and build modals that provide the right information at the right time without trapping users or creating accessibility failures.
- Web App and Frontend Development
Next.js vs Remix vs Astro vs Nuxt in 2026
Next.js, Remix, Astro, and Nuxt each make different architectural bets about how web applications should work. Here is how they compare in 2026 and which one belongs in which project.
- Web App and Frontend Development
React Query vs SWR vs RTK Query
React Query, SWR, and RTK Query all manage server state in React applications, but they make different trade-offs around complexity, bundle size, and Redux integration. Here is how to choose between them.