CRDT vs Last-Write-Wins for Real-Time Collaboration
CRDT vs last-write-wins is a conflict resolution choice. Start with last-write-wins per field and only reach for CRDTs when you have true concurrent editing.
Most teams building a collaborative feature reach for CRDTs because that is what the impressive demos use. That is usually a mistake. CRDT versus last-write-wins is a conflict resolution decision, and for the vast majority of collaborative apps, last-write-wins per field is the correct answer: it is simpler, it is predictable, and users understand it. CRDTs earn their complexity only when you have genuine concurrent editing of the same content, like two people typing in the same sentence. If your app is tasks, records, settings, or comments, you almost certainly do not need them.
What last-write-wins actually does
Last-write-wins, applied per field, says the most recent change to a given field is the one that survives. Two people edit the same task: one changes the title, the other changes the assignee. Both changes stick, because they touched different fields. Two people change the same field, and the later write replaces the earlier one. That is it.
This is not sloppy. It is honest. Users have a clear mental model: whoever saved last wins that field. It matches how a shared spreadsheet or a shared document outline behaves in practice, and it almost never surprises anyone. The key is to resolve at the field level, not the whole record. Record-level last-write-wins throws away half of a merge and feels broken. Field-level last-write-wins preserves independent edits and feels correct.
To do it, every write carries a version or a server-assigned ordering so the backend can tell which change is newer. That ordering lives on the server, for the same reason message order in a chat app lives on the server: client clocks lie. The server decides what "last" means.
What CRDTs buy you, and what they cost
A conflict-free replicated data type is a structure designed so that concurrent changes always merge into the same result, no matter what order they arrive in, with no central referee needed. For collaborative text, this is genuinely powerful. Two people insert characters at the same position, and a CRDT resolves both insertions into a stable, agreed sequence without dropping either keystroke. That is why real-time document editors and multiplayer whiteboards use them.
The cost is real. CRDTs carry metadata for every element so they can merge deterministically, which means memory and payload sizes grow, sometimes a lot, and you have to manage that growth. The logic is harder to reason about, harder to debug, and harder to hire for. You are taking on a distributed-systems dependency to solve a problem you may not have. Paying that cost to let two people edit different fields of a task is pure waste.
How to actually decide
Ask one question: do two users need to edit the same continuous content at the same time and have every keystroke survive? If yes, that is character-level co-editing of text, a shared canvas, a collaborative outline. Use a CRDT for that content. If no, use last-write-wins per field and move on.
Concrete examples. A project tool where people edit tasks, statuses, and assignees: last-write-wins. A CRM where reps update fields on a contact: last-write-wins. A settings page two admins might touch: last-write-wins. A shared document editor where two people type in the same paragraph: CRDT. A design canvas where two cursors move the same shape: CRDT. Notice that even inside a document app, only the document body needs a CRDT. The title, the sharing settings, the comment threads can all be last-write-wins.
You can mix them in one product. Do not let the hardest surface dictate the architecture of the easy ones.
Where this fits in the sync story
Conflict resolution is one piece of a larger system. It sits on top of the operation log and reconciliation flow I described in offline sync for collaborative apps. The log captures what changed; the conflict rule decides who wins when two changes collide. Whichever rule you pick, apply it in one authoritative place on the server so every client converges on the same state.
There is also a delivery question underneath both approaches. Merged results still have to reach every connected client reliably and in order, which is a real-time backend concern, not an application concern. I would rather lean on a platform that handles ordered, durable delivery so I can focus on the merge logic, which is why I run this layer on AltoHost instead of hand-building the transport. If you want the deeper background on that division of labor, I wrote about the backend real-time apps actually need.
My rule after building this more than once: default to last-write-wins, and make yourself justify a CRDT before you adopt one. The right answer is usually the boring one. Complexity you can avoid is reliability you get for free.