A sequence counter can let readers copy shared state without taking the writer’s lock. The reader samples a counter, copies the protected fields, then samples the counter again. A stable even value at both observations indicates that no writer overlapped the copy under the synchronization contract. A changed or odd value forces the reader to discard the snapshot and retry.

This pattern moves work away from reader-side lock ownership, but it does not remove synchronization. Writers still need serialization, counter transitions need defined memory-ordering semantics, and the protected data must remain safe to access during an overlapping write. Those constraints make sequence counters suitable for some read-mostly snapshots and unsafe for data whose lifetime can disappear beneath a reader.

The counter marks writer activity, not object ownership

The common protocol uses an integer sequence value. A writer changes the value from even to odd before mutating protected state, then advances it to the next even value after the mutation is complete.

A reader accepts a snapshot only when its initial and final observations match and are even:

repeat:
    a = sequence
    if a is odd:
        retry

    local_x = shared_x
    local_y = shared_y

    b = sequence
until a == b and b is even

The counter does not grant the reader exclusive access. A writer may run while the reader copies shared_x and shared_y. The validation step detects that overlap and prevents the mixed snapshot from being used.

That distinction separates sequence counters from reader-writer locks. A read lock prevents a conforming writer from entering its critical section. A sequence counter permits overlap and makes the reader responsible for rejecting a potentially inconsistent copy.

Writer serialization is part of the protocol

The odd/even encoding assumes that one writer owns the mutation interval at a time. If two writers independently change the counter and protected fields, the sequence value no longer provides a simple boundary around one coherent mutation.

A practical design therefore serializes writers with a lock or another mechanism. The sequence counter then describes the state of that serialized write section to lockless readers.

Conceptually:

writer lock acquired
sequence becomes odd
mutate protected fields
sequence becomes even
writer lock released

The exact ordering operations cannot be inferred from this pseudocode. A language, runtime, library, or kernel primitive must define the required atomic accesses and barriers. Plain integer loads and stores are not a portable substitute when the programming language permits data races to produce undefined behavior or when the hardware memory model can expose operations in an order that violates the protocol.

Memory ordering connects validation to the copied fields

Two equal counter reads are useful only if the memory model ties them to the data accesses between them. The reader must not observe the final counter value in a way that allows protected-field accesses to escape the intended validation interval. The writer likewise needs ordering that places its state mutation between the transitions that advertise an active and completed write.

Implementations solve this through primitive-specific barriers or atomic ordering rules. Linux sequence-count APIs, for example, provide operations with kernel-defined ordering requirements rather than asking callers to assemble the protocol from arbitrary loads and stores.

At the language level, the relevant rule can be stricter. In C or C++, concurrently reading and writing a non-atomic object without a valid synchronization relation can constitute a data race with undefined behavior. An algorithm that resembles a kernel sequence counter is therefore not automatically valid user-space C++ merely because the generated machine instructions appear plausible on one processor.

The implementation must satisfy both layers: the language memory model and the target synchronization contract.

Retry replaces blocking with potentially repeated work

A reader does not normally wait while holding a sequence-counter read section. If it sees an odd value or detects a changed value after copying, it retries.

That property keeps a reader from directly blocking a writer through reader lock ownership. It also means reader completion depends on writer activity. Under sustained writes, a reader can discard multiple snapshots before obtaining a stable one.

This is a progress trade rather than free concurrency. A reader-writer lock can queue participants and impose a lock policy. A sequence-counter reader instead performs speculative work whose result may be invalidated. The cost depends on snapshot size, write frequency, scheduling, and the implementation’s retry behavior.

Long writer critical sections are especially significant. An odd sequence value tells readers that the protected state is in transition, so readers may spin or retry until the writer publishes an even value. Code using this pattern must account for scheduling contexts in which a writer can be delayed after marking the sequence odd.

Pointer lifetime is a separate hazard

Detecting a concurrent mutation does not make an unsafe memory access safe. Suppose a protected field contains a pointer. A reader can copy that pointer, then a writer can remove and free the referenced object before the reader validates the sequence value.

The reader may eventually detect that the counter changed, but dereferencing freed storage before that validation is already invalid. Retrying cannot reverse the access.

This creates a strict boundary around suitable protected state. Sequence counters work naturally with values that remain addressable while readers copy them: counters, timestamps, coordinate tuples, configuration scalars, and other in-place fields are common shapes. Pointer-rich structures require a separate lifetime mechanism if objects can be reclaimed concurrently.

Reference counting, hazard pointers, epoch-based reclamation, RCU-style schemes, or a lock can provide lifetime guarantees in designs where they fit. The sequence counter can still validate logical consistency, but it cannot replace memory reclamation.

A coherent snapshot can span several fields

The useful property is not limited to one scalar. Consider state containing a base timestamp and a conversion factor that must correspond to the same update. Reading each field independently can combine values from different writer generations.

A sequence-counter read treats the group as one optimistic snapshot:

s0 = begin_read()
base = state.base
scale = state.scale
offset = state.offset
retry = read_changed(s0)

If retry is false according to the primitive’s contract, the copied fields belong to a write-free observation interval. If a writer overlapped the copy, the reader discards all three values together.

This makes the protected invariant explicit. The synchronization boundary is the relationship among the fields, not merely atomic access to each individual field. Making every scalar atomic can prevent torn scalar accesses while still permitting a combination that never existed as one logical state.

Wraparound places a bound on the validation argument

Sequence values have finite width. After enough writer transitions, the counter can wrap and eventually repeat an earlier value. Implementations therefore rely on conditions that prevent a reader from spanning enough completed writes for the counter to return to the same observable value during one read attempt.

The exact bound depends on counter width and primitive semantics. This is another reason to use a defined sequence-count abstraction rather than treating equality of two arbitrary integers as a universal concurrency proof.

For normal short read sections and suitably sized counters, the implementation can make that condition practical. It remains a condition, not a mathematical property of finite counters.

The validation boundary defines what readers may trust

A sequence counter is most precise when treated as a snapshot-validation mechanism. It says that a reader may use copied state only after proving, according to the primitive’s ordering rules, that no serialized writer overlapped the copy.

It does not serialize writers, preserve object lifetime, make racy language-level accesses legal, or guarantee a fixed number of reader attempts. Each of those properties requires another part of the design.

That separation is the main engineering value of the pattern. Writer exclusion, memory ordering, snapshot consistency, progress, and reclamation remain distinct contracts. A sequence counter handles one of them well: detecting that a speculative read crossed a mutation boundary.