Free preview

Why Concurrent Edits Break

In one line: the failure has a precise algebraic shape. Two properties — commutativity and idempotency — are what OT and CRDTs exist to restore, and neither technique makes sense until you see exactly how naive operations violate them.

The data model

A document is a composition of characters in a specific order. Each character has a value and a positional index... The editor performs insert(), delete(), and edit() on characters based on these indexes.

The positional index is the entire source of the problem

This model is the obvious one and it contains the bug.

An operation says "insert 'X' at position 10." Position 10 of what? Of the document as the sender saw it. And the sender's document is a local replica — the design is explicit: "users modify a local copy, so their view may diverge from the server state."

So every operation carries an index that is meaningful only relative to a document state that may no longer exist anywhere.

Alice's replica:  "Educative platform"     -> insert at 10 means one thing
Bob's replica:    "Educative for devs"     -> insert at 10 means something else
Server's state:   neither of the above

Positional indexes are not stable identifiers; they are coordinates in a frame of reference that every writer is changing independently. Every problem in this chapter follows from that one sentence, and every solution is a way of getting a stable identity back:

ApproachHow it fixes the frame problem
OTRewrites the index into the receiver's frame
CRDTAbandons integer indexes for globally unique identities

Two different answers to the same question. Lessons 5 and 6.

Example 1: insertion is not commutative

Two users modify the sentence "Educative by developers" by performing insert() at index 10. Without conflict resolution, two outcomes are possible: "for developers" overwrites "platform" → "for developers"; or "platform" overlaps "for developers" → "platformlopers."

Both users are correct and the results disagree — that is what makes this hard

Notice what is not happening here. Neither user made a mistake, neither operation is invalid, and there is no corruption. Both edits are exactly what their author intended.

The problem is purely that f(g(x)) ≠ g(f(x)) — the operations do not commute, so the outcome depends on an ordering that no one chose and that different replicas may resolve differently.

That is a stronger statement than "there is a race condition." A race condition implies one outcome is correct. Here:

Alice applies her op, then Bob's   -> document X
Bob applies his op, then Alice's   -> document Y
X != Y, and BOTH are legitimate documents

Since every replica applies operations in whatever order they arrive over the network, replicas diverge permanently unless something forces agreement.

When concurrent operations do not commute, replicas that receive them in different orders will not converge — and the fix is either to impose a single order or to make the operations commute. OT does the first; CRDTs do the second. That is the cleanest way to hold the two techniques apart in your head.

The 'platformlopers' outcome deserves a second look

The two failure modes the design names are different in kind.

"for developers" — one insert overwrites the other. An edit is silently lost. Bad, but the document is at least coherent.

"platformlopers" — the two insertions interleave, producing text neither user typed and which is not a word. This is worse: the document is now incoherent, containing a string that exists in no one's intent.

The second is what you get when both operations partially apply at a shared position. And it explains why the naive "last write wins" answer — which the module's earlier chapters used freely for cache entries and location updates — is unavailable here. LWW discards data; in a document, silently discarding a collaborator's paragraph is a serious failure, and there is no version of the document where the loss is visible to anyone.

Example 2: deletion is not idempotent

Two users delete the same character from "EEDUCATIVE." Both intend to remove the extra "E." If both operations execute blindly, the system might delete both instances.

Start:              E E D U C A T I V E
Alice: delete(0) -> E D U C A T I V E      (correct)
Bob:   delete(0) -> D U C A T I V E        (applied to Alice's result: WRONG)

Intent:  remove ONE extra E
Outcome: both E's gone

Idempotency fails because the operation names a position, not a thing

Both users looked at the same document, saw a duplicate "E", and issued delete(0). Semantically these are the same operation issued twice — and applying an operation twice should be indistinguishable from applying it once.

It is not, and the reason is the same as before: delete(0) does not name a character, it names a slot. After the first deletion the slot holds a different character, so the second deletion removes something its author never saw.

Contrast with an operation that names the thing itself:

delete(position 0)             -> "delete whatever is first"   NOT idempotent
delete(character id abc-123)   -> "delete THAT character"      idempotent

The second can be applied any number of times with the same result — the character is either present or already gone.

This is the single strongest argument for CRDTs, and Lesson 6 shows it is exactly what they do: assign every character a globally unique identity so that operations refer to entities rather than to coordinates.

An operation is idempotent when it names an entity; it is not when it names a position. The same principle underlies idempotency keys in payment APIs and message IDs in that building block's at-least-once delivery — and it will return in that building block.

The two rules

Commutativity: the order of applied operations must not affect the final result. Idempotency: repeated application of the same operation must not change the result beyond the initial application.

PropertyWhat breaks without itWhy it fails naively
CommutativityReplicas diverge permanently — each applies operations in arrival orderAn index is relative to a state the receiver may not share
IdempotencyRetries and duplicates corrupt the documentThe operation names a slot, not a character

Idempotency is not optional here, because the network guarantees duplicates

It is tempting to treat idempotency as the smaller of the two — deleting the same character twice sounds like an edge case.

It is not, because the transport makes duplicates routine. Any real-time system delivering over an unreliable network must retry, and retries mean at-least-once delivery. The WhatsApp chapter established the full argument: exactly-once is not achievable end to end, so you build at-least-once delivery plus idempotent application.

So the duplicate operations arriving at a replica come not only from two users pressing delete, but from:

network retry after a timeout
a client reconnecting and replaying its unacknowledged buffer
a replicated queue redelivering after failover

Lesson 8's own design has that third one: the operations queue "can restart on another server and continue processing from the previous state" — a restart that will, at the boundary, redeliver.

Idempotency is what makes at-least-once delivery survivable, and every system in this module that pushes data over an unreliable link needs it.

What the two techniques must supply

Two very different bets on the same problem

The diagram makes the split visible, and it is worth stating before either lesson.

OT keeps the naive data model and fixes the operations. The document stays a list of characters with integer indexes — exactly what a text editor already is. The cleverness lives in a transformation function that rewrites an incoming operation's index to account for operations the receiver has already applied. Cheap data, complicated algorithm, and it needs a central authority to fix the order.

CRDTs keep the naive operations and fix the data model. An insert is just an insert; what changes is that every character carries a globally unique identity and a fractional position, so operations commute by construction. Expensive data, simple algorithm, and no central authority required.

When operations on a shared structure conflict, you can fix the operations or fix the data — and the choice determines whether you need a coordinator. That is the sentence to carry into Lesson 7, where the chapter claims both properties at once.

Key takeaway

The whole problem reduces to one fact: a positional index is a coordinate in a frame of reference that every writer is independently changing. From it, both failures follow — insertion is not commutative, so replicas receiving operations in different orders diverge permanently, with both results legitimate; and deletion is not idempotent, because the operation names a slot rather than a character, which matters constantly since retries and failover make duplicates routine. An operation is idempotent when it names an entity and not when it names a position. The two remedies are structurally opposite: OT fixes the operations and needs a coordinator; CRDTs fix the data and do not.

Next: operational transformation.

Enjoying the preview?

Create a free account to unlock the rest of this course, the in-browser judge, and live AI mock interviews.

Sign up free to continue