Operational Transform Basics
Two people open the same document and start typing at the same time. Alice inserts "Hello" at position zero. Bob inserts "World" at position zero. Both changes are valid; both must appear in the final document. But if each client simply applies both operations in the order they receive them, they diverge: Alice's client ends up with "WorldHello" and Bob's ends up with "HelloWorld". Operational transform is the algorithm that prevents this. It adjusts the position of each operation to account for concurrent operations that have already been applied, so every client converges to the same result.
This guide sits between the sync mechanics of realtime data synchronization and the deeper data-structure approach in conflict-resolution CRDTs.
What you'll learn
Insert and delete operations
OT works with operations: discrete, typed descriptions of a change to a document. For linear text, two operation types cover everything.
Representing edits as typed operations
// Insert: put a string at a position
var insert = { type: 'insert', pos: 3, text: 'Hello ' };
// Delete: remove a run of characters starting at a position
var del = { type: 'delete', pos: 3, len: 6 };
// Apply an operation to a string
function apply(doc, op) {
if (op.type === 'insert') {
return doc.slice(0, op.pos) + op.text + doc.slice(op.pos);
}
if (op.type === 'delete') {
return doc.slice(0, op.pos) + doc.slice(op.pos + op.len);
}
return doc;
}
Both operations describe a change in terms of a character position in the document at the moment the edit was made. That moment is the key detail: a position is only valid relative to the document state it was generated against. Apply two concurrent operations without adjusting their positions and the positions refer to different document states, producing garbage.
The concurrent edit problem
The problem is easy to make concrete. It appears whenever two clients each generate an operation against the same document version and then receive each other's operations.
Position collision without transform
// Initial doc: "Helo"
// Alice (client A) generates: insert at pos 3, text "l" -> "Hello"
// Bob (client B) generates: insert at pos 0, text "Say " -> "Say Helo"
// Without OT:
// Alice applies Bob's op at pos 0 -> "Say Hello" (correct)
// Bob applies Alice's op at pos 3 -> "Say Helo" -> insert "l" at pos 3
// -> "SayHelo" ... wait, pos 3 is now inside "Say " -> "Say" + "l" + " Helo" = wrong
// The same character index means different places after a concurrent insertion.
Bob's insert at position zero pushed every subsequent character four positions to the right. Alice's operation at position three was generated before that shift happened. Without adjustment, Alice's position three now lands inside Bob's inserted text rather than at the intended character.
The transform function
The transform function takes two concurrent operations and produces adjusted versions of each that can be applied in either order to reach the same document.
Adjusting positions for concurrent inserts
// transform(op_a, op_b) -> op_a adjusted for op_b having been applied
function transform(a, b) {
if (a.type === 'insert' && b.type === 'insert') {
if (b.pos <= a.pos) {
// b inserts before a's position: shift a right by b's length
return { type: 'insert', pos: a.pos + b.text.length, text: a.text };
}
return a; // b inserts after a, no adjustment needed
}
if (a.type === 'insert' && b.type === 'delete') {
if (b.pos < a.pos) {
var shift = Math.min(b.len, a.pos - b.pos);
return { type: 'insert', pos: a.pos - shift, text: a.text };
}
return a;
}
if (a.type === 'delete' && b.type === 'insert') {
if (b.pos <= a.pos) {
return { type: 'delete', pos: a.pos + b.text.length, len: a.len };
}
return a;
}
// delete vs delete: handle overlap
if (a.type === 'delete' && b.type === 'delete') {
if (b.pos + b.len <= a.pos) return { type: 'delete', pos: a.pos - b.len, len: a.len };
if (b.pos >= a.pos + a.len) return a;
// overlapping deletes: adjust length and position
var newPos = Math.min(a.pos, b.pos);
var aEnd = a.pos + a.len; var bEnd = b.pos + b.len;
var newLen = Math.max(0, aEnd - Math.max(a.pos, bEnd) +
Math.min(aEnd, b.pos) - a.pos);
return { type: 'delete', pos: newPos, len: newLen };
}
return a;
}
The function covers the four combinations of insert and delete pairs. The most common case, two inserts, is also the simplest: if one inserts before the other, shift the other right by the inserted length. Delete versus delete is the trickiest because deletions can overlap, requiring the lengths to be recalculated to avoid deleting characters that the other operation already removed.
Server-side OT coordination
A client-only OT implementation can guarantee convergence for exactly two clients. More than two requires a server to order operations and apply transforms against the revision log.
The server as the transformation arbiter
// Server maintains the authoritative document + revision log
var doc = '';
var revisions = [];
function applyClientOp(clientOp, clientRevision) {
// Transform op against all server ops since clientRevision
var transformedOp = clientOp;
for (var i = clientRevision; i < revisions.length; i++) {
transformedOp = transform(transformedOp, revisions[i]);
}
// Apply and store
doc = apply(doc, transformedOp);
revisions.push(transformedOp);
return { op: transformedOp, revision: revisions.length - 1 };
}
Each client sends its operation together with the revision number it was generated against. The server transforms that operation against every server operation since that revision, applies it, appends it to the log, and broadcasts the transformed operation and its new revision number to all connected clients. Clients receiving the broadcast apply it directly because it is already transformed against the canonical server state. This is the architecture Google Docs used in its first collaborative editing implementation.
Convergence and correctness
Convergence is the property that all clients end up with the same document state. It is not automatic: the transform function must be correct for every pair of operation types, including the edge cases around overlapping deletions and equal positions.
Testing convergence with round-trip scenarios
The standard test is the diamond: generate two operations against the same state, transform each against the other, apply them both in opposite order on two copies of the document, and verify the copies match. Running this test across hundreds of random operation pairs catches most bugs in a transform implementation. Production OT libraries such as ShareDB and ot.js have extensive test suites for this reason. Building a correct OT implementation from scratch is genuinely hard; the edge cases compound quickly as the operation set grows beyond plain text to rich formatting, embedded objects, and nested structures.
Practical limits of OT
OT is powerful for linear text but has well-known difficulties that have driven the industry toward CRDTs for more complex data structures.
When OT stops being practical
OT requires a server to order operations. In a peer-to-peer or offline-first scenario where clients cannot reach a server, OT breaks down because there is no arbiter to assign a canonical order. The transform function also grows in complexity as the document model grows: adding rich text formatting, embedded media, or tree-structured content requires writing new transform cases for every new pair of operation types, and the combinatorial explosion of edge cases makes correctness proofs difficult. CRDTs, covered in the next guide, solve both problems at the cost of more complex data structures. For linear text in a server-coordinated system, OT remains a solid, well-understood choice, and its client-side API, a stream of typed operations to apply, is the same regardless of the coordination mechanism underneath.
Frequently Asked Questions
What is operational transform?
Operational transform is an algorithm that lets two clients apply concurrent edits to a shared document and arrive at the same result. When two operations are generated against the same document state, OT adjusts each one so it can be correctly applied after the other without producing inconsistent output.
Why does inserting text before a cursor shift it?
Character positions are indices into a string. Inserting text before position N shifts everything at N and beyond one position to the right for each inserted character. An operation generated before that insertion was applied must have its position adjusted upward to still refer to the same content.
What is convergence in OT?
Convergence means that all clients end up with the same document state after applying the same set of operations, regardless of the order in which they received and applied them. A correctly implemented OT algorithm guarantees convergence for any combination of concurrent operations.
How is OT different from CRDTs?
OT transforms operations against each other and typically requires a central server to order them. CRDTs use data structures designed so that any two replicas can be merged without coordination. CRDTs work peer-to-peer and handle network partitions better; OT is simpler to understand for linear text in a server-coordinated system.
Read next: the Interactive Web hub, or continue to conflict-resolution CRDTs for the peer-to-peer alternative to OT.
Want to see how document models connect to Backbone views?
The Backbone guide shows views that update cleanly when an underlying model changes.
Explore the Backbone Guide →