skip to content

questions

6

What problem does a CRDT (Conflict-free Replicated Data Type) solve, and how does a G-Counter's merge rule guarantee replicas converge without any coordination between nodes?

level: juniorimportance: must knowfreq 70%

answer

  1. max, not sum, for merge
  2. per-node slot, sum for read
  3. strong eventual consistency
  4. no coordinator needed
  5. grow-only = no decrement

basics

~20 s

A CRDT lets many computers update the same piece of data at the same time without talking to each other, and their copies can always be combined back into one correct answer later. A G-Counter only grows, so merging two copies means keeping the bigger count each machine reported.

solid answer

~40 s

A CRDT (Conflict-free Replicated Data Type) is a data type whose replicas can be updated concurrently, independently, and offline, then merged deterministically without coordination, because the merge function is mathematically guaranteed to converge. The classic example is the G-Counter (grow-only counter): each replica keeps a per-node counter of its own increments instead of one shared integer. To read the total you sum all per-node slots; to merge two replicas you take the element-wise maximum of each node's slot. Because 'max' is commutative, associative, and idempotent, replaying merges in any order, any number of times, or dropping duplicate messages always converges to the same total - solving eventual consistency without locks, consensus, or a coordinator.

go deeper

for a junior

Should describe, in plain terms, that CRDTs let replicas update independently and merge safely, and walk through the G-Counter idea (each node owns a slot, read = sum, merge = max) even if the terms 'commutative/associative/idempotent' aren't yet fluent.

for a middle

Should name the merge rule precisely (element-wise max) and explain why sum would be wrong (breaks idempotency), and know that decrement needs a different structure.

for a senior

Should connect the mechanism to the broader guarantee (strong eventual consistency), articulate the coordination cost it avoids, and know at least one production system that ships this natively.

for a principal

Should reason about when this trade (no coordination, unbounded per-replica metadata growth) is and isn't worth it for a given system's replica count and churn pattern, and connect it to broader consistency-model choices across the architecture.

## The conflict a CRDT sidesteps In a normal distributed system, when two nodes hold copies of the same mutable value and both are updated independently while disconnected, you get a conflict: which write wins? Traditional systems solve this with: - **locks**; - **consensus protocols** (Raft, Paxos); - **last-writer-wins timestamps** that quietly discard one of the updates. A **CRDT** sidesteps the conflict entirely by designing the data type itself so that `merge` is a well-defined, order-independent operation — no matter which order updates arrive in, how many times a message is duplicated, or which subset of replicas have seen which updates yet, applying the merge function to any set of replica states always produces the same final result. This guarantee is called **strong eventual consistency**: once all replicas have seen the same set of updates (in any order), they hold identical state, with no explicit conflict resolution step and no need for replicas to be online or reachable at the same time. ## How a G-Counter works A **G-Counter** (grow-only counter) is the simplest CRDT and a good vehicle for the general idea. Instead of representing the counter as one shared integer, each replica (A, B, C) owns a private slot in a vector: `{A:0, B:0, C:0}`. 1. When A increments, it only ever bumps its own slot: `{A:1, B:0, C:0}`. 2. The counter's value is always the sum of all slots. 3. To merge two replica states you take the **element-wise maximum** of each slot, not a sum — merging `{A:3,B:1,C:0}` with `{A:2,B:1,C:4}` yields `{A:3,B:1,C:4}`. Because each node only increases its own slot and merge takes the max, no increment is ever lost (max retains the largest count either replica had recorded) and no increment is double-counted (max, not sum, so re-merging the same state twice changes nothing). ## What coordination would cost instead The alternative — shipping every increment to a single owner, or running a consensus protocol so all nodes agree on a serialized order before applying an update — costs a network round trip (or a quorum) per write, and stalls writes during a network partition. That's fine for a bank ledger, but for many practical counters (like counts, shopping carts, presence indicators) you'd rather let every replica accept writes locally, instantly, even fully offline, and reconcile later. CRDTs make that reconciliation mathematically guaranteed to succeed rather than requiring bespoke per-feature conflict logic — the appeal behind **offline-first** apps and **multi-region active-active** designs, where writes never block on other regions being reachable. ## What you give up The cost is **expressiveness and space**. - A G-Counter can only grow — it structurally cannot support decrement (that needs a **PN-Counter**, built from two G-Counters). - More generally, CRDTs only exist for operations whose combined effect can be expressed as a **commutative, associative, idempotent** merge; not every abstract data type admits such a design, and some (like a map with complex nested invariants) require careful structural tricks (unique per-op tags, tombstones) to make merges well-defined. - Complex CRDTs like sets carry per-element metadata that can dwarf the actual payload. ## How teams get it wrong 1. A team new to CRDTs commonly reaches for a plain replicated integer, lets each node add or subtract directly, and **resolves** conflicts with last-writer-wins — this silently drops updates (a concurrent increment from another node just vanishes). 2. Another mistake is merging a G-Counter-shaped vector with **sum** instead of **max**, which double-counts increments every time the same state is re-merged, breaking idempotency. 3. A third failure mode is ignoring the vector's unbounded growth — with high replica churn (autoscaling instances each getting a slot), the vector never shrinks, since a slot can't be safely removed while any other replica might still hold stale state referencing it. ## Where it ships - **Riak KV** (Basho) built native CRDT data types (counters, sets, maps, registers) directly into its replicated key-value store so applications get conflict-free merges without hand-rolling resolution logic. - **Redis Enterprise's CRDB** (Conflict-free Replicated Database) uses CRDTs for active-active geo-replication across regions. - Collaborative editors built on **Automerge** or **Yjs** use CRDT-based sequences so multiple users can type concurrently offline and merge edits deterministically when they reconnect.

  • Why can't a G-Counter support decrement directly?
    Because merge takes the max of each replica's own slot, a decrement would look identical to 'this replica reset and hasn't caught up yet,' so max would silently discard it. Decrement requires a separate structure - a PN-Counter pairs two G-Counters, one tracking increments and one tracking decrements, and the value is (sum of increments) minus (sum of decrements).
  • What happens if a merge message is delivered twice?
    Nothing changes - merge is idempotent by design (max of a value with itself is itself), so duplicate delivery, common in unreliable networks, is automatically safe and requires no deduplication logic.
  • Does a G-Counter need vector clocks or version numbers?
    No - the per-node slot vector already encodes 'how much I know from each replica,' which serves the same causality-tracking purpose a vector clock would, without a separate mechanism.

Like several people independently topping up a shared water tank from their own hose, and reading the total by adding up everyone's own contribution gauge - even if two people top up 'at the same time' or the readings arrive out of order, summing each person's own gauge always gives the right total.

saying these in an interview costs you the question

  • Says CRDTs need a central coordinator to merge
  • Merges a G-Counter's slots with sum instead of max
  • Thinks last-writer-wins is the same thing as a CRDT merge
  • Can't explain why decrement needs a second counter
  • Assumes CRDTs work for any data type with no design constraints

context

open as a page

What is the structural difference between a state-based CRDT (CvRDT) and an operation-based CRDT (CmRDT), and what does each approach require from the network layer to guarantee convergence?

level: middleimportance: must knowfreq 60%

basics

~20 s

One kind of CRDT sends its whole current value to other computers, and they combine values together (state-based). The other kind sends just 'what changed' as a small message, applied in a way that works regardless of order (operation-based). The first tolerates lost or duplicate messages better; the second sends less data but needs more careful delivery.

open as a page

What three algebraic properties must a CRDT's merge function satisfy to guarantee replicas converge, and how does an LWW-Register (last-write-wins register) satisfy them while still having a well-known failure mode?

level: seniorimportance: must knowfreq 50%

basics

~20 s

A CRDT's merge rule has to give the same answer no matter which order you combine copies in (commutative), no matter how you group multiple merges together (associative), and merging something with itself again changes nothing (idempotent). An LWW-Register just keeps whichever write has the latest timestamp - that math works, but it means one of two concurrent writes is always thrown away, which can silently lose data.

open as a page

In an OR-Set (observed-remove set) CRDT, how are add and remove operations tracked so that a concurrent add and remove of the same element resolves correctly, and why does a naive two-phase-set (an add-set plus a tombstone remove-set) fail on this case?

level: seniorimportance: must knowfreq 55%

basics

~30 s

An OR-Set tags every 'add' with a unique ID, and a 'remove' only cancels the specific adds it has actually seen. So if one replica adds an item while another replica, not knowing about that add yet, tries to remove the same-named item, the new add survives because its unique tag was never observed by that remove. A simpler design that just marks a name as 'removed forever' would wrongly block that later add too.

open as a page

How does a PN-Counter support both increment and decrement using only grow-only counters internally, and why doesn't it work to just merge a single counter value using max even if deltas can be negative?

level: middleimportance: should knowfreq 55%

basics

~20 s

A PN-Counter is really two separate 'only goes up' counters glued together - one counts all the increments, one counts all the decrements - and the real value is increments minus decrements. You can't just keep one number and take the max across replicas, because max can't tell a smaller number from 'this replica went down on purpose' versus 'this replica hasn't caught up yet.'

open as a page

When operating CRDTs at scale in production, what are the concrete costs (metadata growth, tombstone accumulation, causal stability tracking) and in what situations should you avoid choosing a CRDT for a piece of state at all?

level: principalimportance: should knowfreq 35%

basics

~20 s

CRDTs make merging automatic, but the bookkeeping (unique tags, deleted-item markers, per-replica counters) keeps growing and someone has to periodically clean it up safely, which is tricky. And CRDTs are the wrong tool whenever you need a strict rule across the whole system at once, like 'account balance can never go negative,' because no replica can enforce that alone without talking to the others first.

open as a page