Building real-time collaborative web applications (such as collaborative document editors, whiteboard canvases, and multi-user spreadsheets) without centralized locking requires provable strong eventual consistency. Conflict-Free Replicated Data Types (CRDTs) solve concurrent mutation anomalies mathematically by framing data models as either State-Based Convergent (CvRDT) semilattices or Operation-Based Commutative (CmRDT) causal broadcast streams, guaranteeing identical converged state across all clients.
The Mathematics of Bounded Join-Semilattices (CvRDT)
Why State-Based CRDTs converge deterministically via least-upper-bound operations:
A state-based CRDT merge function $\sqcup$ forms a bounded join-semilattice satisfying three invariant algebraic properties: Commutativity ($A \sqcup B = B \sqcup A$), Associativity ($(A \sqcup B) \sqcup C = A \sqcup (B \sqcup C)$), and Idempotence ($A \sqcup A = A$). Regardless of network packet reordering or duplicate delivery, state convergence is mathematically guaranteed.
State-Based (CvRDT) vs Operation-Based (CmRDT) Architecture Matrix
| CRDT Architectural Model | Network Wire Payload | Transport Requirements | Garbage Collection Overhead |
|---|---|---|---|
| State-Based (CvRDT) | Full or Delta-State Payloads | Unreliable / Out-of-order OK | Simple (State overwrites) |
| Operation-Based (CmRDT) | Minimal Delta Operations | Strict Causal Exactly-Once Delivery | Complex (Causal buffer pruning) |
Positive-Negative Counter (PN-Counter) Implementation in TypeScript
Constructing a deterministic state-based distributed counter in TypeScript:
export class PNCounter {
constructor(
public readonly nodeId: string,
public p: Map<string, number> = new Map(),
public n: Map<string, number> = new Map()
) {}
public increment(amount = 1): void {
const current = this.p.get(this.nodeId) || 0;
this.p.set(this.nodeId, current + amount);
}
public decrement(amount = 1): void {
const current = this.n.get(this.nodeId) || 0;
this.n.set(this.nodeId, current + amount);
}
public read(): number {
const totalP = Array.from(this.p.values()).reduce((sum, v) => sum + v, 0);
const totalN = Array.from(this.n.values()).reduce((sum, v) => sum + v, 0);
return totalP - totalN;
}
public merge(remote: PNCounter): PNCounter {
const mergedP = new Map<string, number>();
const mergedN = new Map<string, number>();
const allKeys = new Set([...this.p.keys(), ...remote.p.keys(), ...this.n.keys(), ...remote.n.keys()]);
allKeys.forEach((key) => {
mergedP.set(key, Math.max(this.p.get(key) || 0, remote.p.get(key) || 0));
mergedN.set(key, Math.max(this.n.get(key) || 0, remote.n.get(key) || 0));
});
return new PNCounter(this.nodeId, mergedP, mergedN);
}
}
Engineer Resilient Real-Time Collaborative Web Applications
Eliminate merge conflicts and unlock real-time peer-to-peer web applications. Read our guide on Deterministic State Machine Replication & Raft, explore syndicated mezzanine finance on FinanceQuickly Private Credit, review rollover biomechanics on CarInjuryAttorney Forensics, or partner with our distributed systems engineers.