Why architecture matters here
The architecture matters here because the requirements pull in opposite directions and only a specific structure satisfies both. Users demand that a keystroke appear instantly — a text editor that waits for a server round trip before painting a character is unusable at any latency above about thirty milliseconds, and the internet does not offer that. But correctness demands that all replicas converge on one document. Instant local feedback means applying an edit before you know what anyone else did; convergence means reconciling with what they did. Every collaborative editor is a negotiation between those two facts, and OT is one of the two principled ways to have both.
What makes the negotiation hard is that the reconciliation must be invisible. In most distributed systems, conflict resolution can surface to the user: a merge conflict in version control stops and asks. A collaborative editor cannot stop and ask, because the conflict happens every few hundred milliseconds and the user is mid-sentence. The resolution has to be automatic, and it has to preserve what OT literature calls intention — not merely that both documents end up identical, but that they end up in the state each author would recognize as containing their edit. Two replicas can converge on garbage; convergence alone is a weak guarantee.
The failure mode is also uniquely punishing, which raises the architectural stakes. When OT is wrong, nothing crashes. There is no exception, no failed health check, no error rate to alert on. Two documents simply drift apart, and the users notice minutes later when one of them scrolls up and sees a sentence the other does not have. By then the operation log is thousands of edits long and the transformation bug is buried somewhere in the middle. This is why OT implementations are dominated by invariant checking rather than by exception handling: the system must actively look for divergence, because divergence will not announce itself.
Placement in the stack follows from all of this. OT cannot live in the database, because the database sees writes, not intentions — by the time an edit is a row update, the information about what the user meant relative to what they were looking at is gone. It cannot live purely in the client, because someone must impose an order and be the source of truth for late joiners and reconnecting clients. OT is an application-layer protocol with a client half and a server half that must agree exactly, which is why mixing OT client and server versions is a genuine compatibility hazard rather than a routine deploy.
The architecture: every piece explained
The first piece is the operation model, and its design constrains everything downstream. An operation must be small, total, and expressed relative to a known document state. In the classic plain-text formulation there are two: insert a character (or string) at a position, and delete a run of length n at a position. Real editors extend this with attribute operations for bold and links, and most production systems use a retain/insert/delete encoding where an operation is a sequence of instructions that walks the whole document — retain 40 characters, insert 'hello', delete 3, retain the rest. That encoding matters because it makes the operation's length explicit, which turns whole classes of transformation bugs into cheap assertion failures.
The second piece is the transformation function, conventionally written T. Given two operations a and b that were both generated against the same document state, T produces a pair (a', b') such that applying a then b' gives the same document as applying b then a'. This is the property known as TP1, or convergence: it says the diamond closes. Concretely, if a inserts at index 5 and b deletes index 2, then a' — a rewritten to live in the world where b already happened — inserts at index 4 instead. The whole intelligence of the system is in this function, and it must handle every pairing of operation types, including the awkward ones like two inserts at the identical position, where a tie-break rule (usually by site identifier) decides who goes first so that both replicas break the tie the same way.
There is a second, harsher property called TP2, which says that transformation is consistent when three or more operations transform against each other in different orders. TP2 is genuinely difficult to satisfy, and this is the single most important architectural fact about OT: most production systems avoid needing it rather than achieve it. They do so by introducing a central server that imposes a total order on operations, which means no client ever has to transform against a set of concurrent operations in an ambiguous order. Every client transforms against the server's linear log. This is the Jupiter model, and it is why real-world OT editors are not peer-to-peer.
The third piece is the client state machine. Each client holds the document, a revision number identifying which server state it is synchronized to, and a queue of operations it has applied locally but that the server has not yet acknowledged. The queue is the client's memory of the gap between what it shows and what is official. Most implementations keep this deliberately simple: send one operation at a time, hold the rest in a buffer, and compose buffered operations together so that the outstanding set is never more than a single sent operation plus a single composed pending operation. That constraint keeps the state machine small enough to reason about.
The fourth piece is the server, whose job is narrower than people expect. It does not merge, and it does not resolve conflicts in any semantic sense. It assigns a total order, transforms each arriving operation against every operation already in the log that the sender had not seen (determined by comparing the sender's claimed revision to the current one), appends the result, bumps the revision counter, and broadcasts. The revision counter is the shared clock that makes the whole protocol tractable — it turns the question 'what did this client not know?' into simple arithmetic on the log, which is the substitution that makes OT practical.
End-to-end flow
Follow a single keystroke. A user types a character in the middle of a paragraph at revision 40. The editor immediately applies the insert to the local document and paints it — this is the non-negotiable step, and it happens before any network activity, which is why collaborative editors feel local even on bad connections. The operation is then tagged with revision 40, meaning 'this edit was authored against the world as of server revision 40', and handed to the sync layer.
The sync layer checks whether an operation is already in flight. If the client is waiting on an acknowledgment for an earlier operation, the new one does not go out; it is composed into the pending buffer instead. This one-in-flight discipline is what bounds the client's state space. If nothing is outstanding, the operation is sent with its revision number and moves into the awaiting-ack slot.
The server receives an insert tagged revision 40 while its own log is at revision 43, because two other clients committed edits in the meantime. The gap tells the server exactly what this client did not see: operations 41, 42, and 43. It transforms the incoming operation against each of those in sequence — first against 41, producing an operation that makes sense in the world after 41; then that result against 42; then against 43. The output is an operation that is correct against the current server document. The server applies it, calls it revision 44, and broadcasts it to every client including the author.
The author's client receives revision 44 and recognizes it as the acknowledgment of its own outstanding operation. It does not re-apply it — the character is already on screen. It advances its revision number to 44, clears the awaiting-ack slot, and if a pending buffer accumulated while waiting, sends that next. The round trip is invisible to the user, who has been typing the whole time.
The other clients receive revision 44 as a genuinely new operation, and this is where the second transformation happens. A client that has its own unacknowledged edit in flight cannot apply the server's operation verbatim, because the server's operation was computed against a document that does not include the local pending edit. So the client transforms the inbound operation against its own outstanding and pending operations, applies the transformed result to the local document, and — crucially — also rewrites its own outstanding operations against the inbound one, so that when they eventually reach the server they will be expressed correctly. Transformation is symmetric: both sides of the diamond get rewritten.
The synthesis worth internalizing is that there are exactly two transformation points, and confusing them is the most common source of bugs. The server transforms an inbound operation forward across the log gap; the client transforms an inbound operation across its own local queue while simultaneously transforming that queue across the inbound operation. Both use the same function T. Neither side needs to know anything about the other's internal state beyond the revision number, which is the property that lets clients disconnect, reconnect, and catch up by simply asking for everything since their last known revision and replaying it through the same code path they use when live.