What is a compare-and-set (CAS) operation, and why is it typically the primitive used to build linearizable writes and distributed coordination on top of a replicated store?
answer
- atomic read-check-write, single step
- fails loudly instead of losing updates silently
- underlies locks, leader election, idempotency keys
- only safe atop a linearizable store
- guard against ABA with version/fencing tokens
basics
~20 sCompare-and-set writes a new value only if the current value still matches what you expected. It lets many clients race to update the same item safely, because only one 'wins' at a time -- no lost updates.
solid answer
~40 sCompare-and-set (CAS) is an atomic operation: 'set key K to newValue, but only if K currently equals expectedValue; otherwise fail and return the actual current value.' The read-check-write happens as one indivisible step, so nothing can sneak in between the check and the write. CAS is the building block for linearizable coordination because it lets a client update shared state without a lost-update race: to acquire a lock, a client CAS's the lock key from 'unlocked' to 'held-by-me'; if it fails, someone else got there first. Leader election, unique-claim idempotency keys, and optimistic concurrency control are all CAS loops. CAS is only correct atop a linearizable store -- if reads could be stale, a client could pass its check against an outdated value and overwrite a newer one, silently corrupting the coordination.
go deeper
Should know CAS means 'update only if the value hasn't changed since I last checked' and that it's used to avoid two people overwriting each other.
Should be able to describe the atomic check-and-write semantics precisely and name at least one use case, such as a distributed lock or optimistic concurrency control.
Should connect CAS's correctness to the underlying store being linearizable, explain what breaks if it isn't, and discuss retry/backoff behavior under contention.
Should be able to discuss the ABA problem and fencing tokens, design a CAS-based coordination scheme (e.g. leader election or idempotent claim) end to end, and reason about contention/thundering-herd behavior at scale.
## What compare-and-set does Compare-and-set is an **atomic read-modify-write primitive**: a client submits a triple of (key, expected current value, new value), and the store applies the write only if the key's current value equals the expected value at the moment the operation executes; otherwise it rejects the write and typically returns the actual current value so the client can retry. The **atomicity** is the whole point — between the compare and the set there is no window where another client's concurrent operation could interleave, because the store executes the check-and-write as a single indivisible step, usually implemented internally with one of: - a **lock**; - a **single-writer sequencer**; - a **consensus-committed log entry**. ## Why it exists CAS exists because plain reads followed by separate writes are unsafe under concurrency: if client A reads a value, decides on a new value, and then writes it, a client B could have read and written in between, and A's write would silently clobber B's update — the classic **lost-update anomaly**. CAS collapses the read-decide-write sequence's vulnerable middle into an **atomic guard**, so at most one of several racing clients can succeed with any given expected value, and everyone else finds out immediately via a failed CAS that they need to re-read and retry. This turns a race condition into an explicit, observable conflict the application can handle, rather than a silent data-loss bug. It is the mechanism underneath essentially every distributed coordination primitive: - a **distributed lock** is a key CAS'd from "free" to a token identifying the holder; - **leader election** is a term-number key CAS'd upward by whichever candidate gets there first; - an **idempotency key** is a "claimed" flag CAS'd from absent to present so a retried request can detect it already ran; - **optimistic concurrency control** on a document store CAS's a version number so a client can detect it edited a stale copy before overwriting someone else's changes. ## The dependency that trips people up The critical dependency, and the part that trips people up, is that **CAS is only meaningful atop a linearizable store**. If the underlying storage can return stale reads — for instance, because a CAS is evaluated against a follower replica that hasn't caught up, or because the store offers only eventual consistency — then two different clients could each observe the same stale "expected" value and both have their CAS succeed against two different replicas that haven't yet reconciled, defeating the entire purpose of the primitive: it was supposed to guarantee only one winner. This is exactly why systems that expose CAS as an API go out of their way to route CAS operations through a mechanism that guarantees linearizability for that key — a `Raft` leader in etcd/ZooKeeper's case, or a single partition owner in DynamoDB's case: - etcd's transaction API; - ZooKeeper's version-checked writes; - DynamoDB's conditional writes tied to a single-partition-key linearizable execution. ## The cost The cost of CAS-based coordination is **contention and retries**, not just raw latency. - **Thundering herd.** Under high concurrency, many clients competing to CAS the same key produces a thundering herd of failed attempts and retries, so systems built on CAS loops need backoff and jitter, and often a queueing or leader-election layer on top so only one client attempts a given piece of work at a time rather than everyone racing on every attempt. - **The ABA problem.** A subtle failure mode: a value changes from A to B and back to A between a client's read and its CAS; the CAS succeeds because the value matches the expected "A" again, even though meaningful state changed in between. Systems guard against this by CAS'ing on a **monotonically increasing version number or fencing token** rather than the raw value itself, so a round-trip back to the same value is still detectable as a different version. ## Where it shows up A concrete real-world example: Kubernetes controllers write to `etcd` using optimistic concurrency — every object carries a `resourceVersion`, and updates are effectively CAS operations conditioned on that version. If a controller reads an object, computes a change, and another controller updates the object first, the writer's CAS fails with a conflict and the controller re-reads and retries, which is exactly how Kubernetes avoids two controllers silently overwriting each other's changes to the same resource under heavy concurrent reconciliation.
- What happens if two clients CAS the same key at the exact same time with the same expected value?The store still processes them one at a time internally, because CAS is atomic; exactly one of them observes success and the other observes failure with the now-current value returned, even though from the clients' outside view the requests looked simultaneous.
- Why is a plain 'read then write' pattern unsafe for implementing a lock, compared to CAS?Because there's a window between the read and the write where another client can also read the same 'unlocked' state and also decide to write 'locked', so both clients believe they hold the lock -- CAS closes that window by making the check and the write one atomic step.
- How does the ABA problem defeat a naive CAS-based algorithm, and how do systems typically prevent it?If a value goes from A to B and back to A between a client's read and its CAS, a value-based CAS check against 'A' succeeds even though something meaningful happened in between, which can corrupt algorithms that assumed no state changed. Systems prevent this by CAS'ing on a monotonically increasing version number or fencing token instead of the raw value, so the round trip back to 'A' still shows a different version.
It's like a bank teller who will only process your withdrawal slip if the balance printed on it still matches the account's current balance exactly; if someone else's transaction changed the balance in the meantime, the teller hands the slip back untouched instead of guessing, and you have to check the new balance and try again.
saying these in an interview costs you the question
- Describes CAS as just 'a fast write', missing the conditional check entirely
- Assumes CAS is safe on top of an eventually consistent or stale-replica read path
- Cannot explain the lost-update problem CAS is solving
- Doesn't know CAS can fail and that failure must be handled (retry/backoff), not ignored
- Unaware of the ABA problem or why version/fencing tokens matter