A ring buffer can report “empty” when it is actually full if its empty check compares only the read and write cursors modulo the buffer capacity. In Morgan Ma’s example, four pushes into a four-slot ring make both cursor residues zero, even though all four slots are occupied. The underlying problem is that modulo arithmetic discards how many times a cursor has gone around the ring.
How a full ring can look empty
Ma’s example uses monotonically increasing read and write cursors, named r and w, for a four-slot buffer. The physical slot for a cursor is selected with its value modulo the capacity. If the empty check compares those residues, then it treats equal positions as proof that no data is stored.
After four pushes and no pops, the raw cursors are conceptually r = 0 and w = 4. Their positions within the ring are both zero: r % 4 == w % 4. The comparison says “empty,” but the difference between the cursors says four items have been written and none read. In the article’s illustrative program, the output after the four pushes is empty=true and popped=0.
This is state aliasing: mapping cursors to ring positions loses the lap count. Equal modulo positions can represent either zero items or a whole number of capacities of items. A residue-only comparison cannot distinguish those states.
#1 Best Overall
What state distinguishes empty from full?
For the sequential design described by Ma, the useful occupancy invariant is w - r. If the read cursor never advances beyond the write cursor, that difference represents the number of occupied elements. The empty and full checks can then be expressed separately:
empty()whenw - r == 0.full()whenw - r == capacity.
The article’s second sketch uses std::size_t cursors and a vector, calculates occupied() as w - r, and refuses a push when the ring is full. It is an illustrative approach for the example, not a universal production fix: cursor wrap and concurrency still require deliberate design.
How to reproduce and diagnose the boundary error
Ma recommends first testing the ring sequentially with a deliberately small capacity. A four- or eight-slot ring makes the collision easy to reach and inspect.
- Set the capacity to four or eight slots.
- Test pushes at
capacity - 1,capacity, andcapacity + 1. Define what should happen at each boundary, especially whether a push at capacity must be rejected. - Record the raw read and write cursors after each operation, along with their residues modulo capacity.
- Compare
w - rwith the number of elements visibly stored. Check that the occupancy remains between zero and capacity under the intended push and pop behavior. - Only after the sequential behavior has a reliable oracle, add threads and investigate concurrency separately.
The useful diagnostic is not just “the ring says empty.” Inspect the raw cursor values and their residues at the failure point: equal residues show the matching physical position, while the raw cursor difference reveals whether the ring has made a complete lap.
Crashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minutePC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11Why memory-safety tools may not catch this
The article characterizes this failure as an invariant or logic error, not necessarily an invalid memory access. A program can access valid slots and still make the wrong decision about whether data exists. Passing a sanitizer run therefore does not, by itself, prove that the ring’s full/empty protocol is correct.
Ma recommends treating race detection as a later, separate step: a thread race is a different failure mode from the sequential boundary collision. The article names AddressSanitizer, UndefinedBehaviorSanitizer, ThreadSanitizer, GDB, and compiler commands in its workflow, but the central check for this bug is first to compare the implementation’s empty/full decisions with a sequential occupancy oracle.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Limits of the example
Ma presents proposed test examples rather than a production incident dump, and the example does not establish a concurrency solution or prove wait-free behavior. The article also notes that finite-width cursors can wrap during long runs; its occupancy subtraction assumes the read cursor does not outrun the write cursor. Those properties need to be addressed for the specific implementation rather than inferred from the four-slot demonstration.
The article discloses that it was prepared as part of MonkeyCode product outreach. Ma says free model access and a free server option were used to draft boundary tests and compile throwaway variants, while candidate outputs were compiled locally. The article cautions that a remote compile is not a sanitizer run. That disclosure is context for the example, not independent validation of a product or a recommendation to use one.
Best Value
Source
Morgan Ma, “The Empty Check Passed on a Full Ring”, DEV Community, posted September 20 (the year is not established in the available article text).
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




