A real-time collaborative code editor that actually works. No servers, no complicated setup – just open it in multiple browser tabs and start typing.
I built this to learn how Google Docs does real-time collaboration without everyone's edits destroying each other. Turns out the answer is CRDTs (Conflict-free Replicated Data Types) and they're pretty cool once you wrap your head around them.
The interesting part isn't the editor itself – it's that multiple people can edit the same spot at the same time and it just... works. No "user locked this line" nonsense, no merge conflicts, everyone's changes survive.
Live demo: https://aeron-gh.github.io/collabcode/
Open it, then open the same link in another tab. Type in one tab, watch it appear in the other. Try typing in both at once – everything stays in sync.
Instead of sending the whole document every time someone types, we send tiny "operations" like "user A inserted 'x' at position 5". Each operation has a timestamp (technically a vector clock) so all the tabs can agree on what order things happened in.
The tricky part is handling deletions. If I delete character 5 and you're trying to insert at character 6 at the same time, things get weird. The solution is "tombstones" – we don't actually delete anything, we just mark it as invisible. Sounds wasteful but it prevents all kinds of bugs.
You'll need a web server because file:// URLs don't support the localStorage events we use for syncing between tabs.
# Clone it
git clone https://agarawala.github.io/collabcode
cd collabcode
# Start any web server
npx serve
# or
python -m http.server 8000
# Open http://localhost:3000 (or :8000)Then open that same localhost URL in 2-3 tabs and try typing.
Pure vanilla JavaScript. No React, no Vue, no build step. I wanted to understand the algorithms without framework magic getting in the way.
The only external dependency is Google Fonts for the monospace fonts. Even that's optional – it'll fall back to system fonts if you're offline.
Race conditions during concurrent edits
When two people type at the same position simultaneously, naive implementations just pick one and lose the other. Vector clocks fix this by establishing a deterministic order that all clients agree on.
Cursor jumping after remote operations
Your cursor is at position 10. Someone else inserts 5 characters at position 3. Now your cursor should be at 15, not 10. Had to implement operational transformation to adjust cursor positions based on what operations happened.
Out-of-bounds errors after deletions
Alice deletes character 5. Bob tries to insert at character 6 (which no longer exists). Tombstones solve this – we keep deleted characters in the data structure, just mark them invisible.
Infinite loops
Setting the textarea value triggers an input event, which updates the CRDT, which sets the textarea value, which triggers... yeah. Simple flag to prevent that.
Memory leaks
Tombstones accumulate forever. Added garbage collection to clean up old ones after they're no longer needed.
index.html- The actual editor interfacestyles.css- Terminal-style brutalist designcrdt.js- The core algorithm (vector clocks, character IDs, tombstones)sync-engine.js- Handles syncing between tabs via localStorageeditor.js- Manages the textarea and UI updatesapp.js- Wires everything together
Because this runs on GitHub Pages (static hosting only). In production you'd replace the localStorage sync with WebRTC for actual peer-to-peer, or WebSockets if you have a server.
The cool part is the CRDT algorithm doesn't care – swap out the sync layer and everything else stays the same.
See DEPLOYMENT.md for detailed instructions, but the short version:
git init
git add .
git commit -m "first commit"
git remote add origin https://agarawala.github.io/collabcode
git push -u origin mainThen enable GitHub Pages in your repo settings (Settings → Pages → Source: main branch).
Your site will be live at https://YOUR-USERNAME.github.io/collabcode/
CRDTs are elegant but debugging them is hell. You can't just look at the text and see what's wrong – you have to trace through operation histories and vector clocks.
The hardest part wasn't the algorithm itself (plenty of papers explain that). It was all the edge cases: What if operations arrive out of order? What if the same operation arrives twice? What if someone's clock is way ahead?
Also learned that "deterministic" doesn't mean "simple". The character ordering logic has like 3 levels of tiebreakers to handle all the edge cases.
- Use a proper CRDT library instead of rolling my own (though I learned more this way)
- Add syntax highlighting (Monaco editor or CodeMirror)
- Real WebRTC instead of localStorage hack
- Better cursor rendering (right now it's just approximate positioning)
- Undo/redo that's CRDT-aware
This started as a learning project but if you want to improve it, go ahead! Open an issue or PR.
The code has comments explaining the tricky parts. If something's confusing, that's probably a place where I need better comments.
- CRDT Tech Report - The paper that explains how this all works
- Logoot Algorithm - Similar approach to character ordering
- Google's OT explanation from the Wave days (now archived)
- Lots of trial and error with console.log
MIT - do whatever you want with it
Built because I was curious how Google Docs works. Turned out to be way more complicated than I expected, but now I get it.
If you find bugs (you probably will), open an issue. If you fix bugs, even better.