With front-plus-count storage, empty means and full means ; every allocated slot is usable. With only the two wrapped array indices , the equality describes both possibilities. Indeed , and the two allowed counts have the same remainder. Recovering the count as therefore loses the full state. A count, a separate full flag, or a different index representation must resolve that ambiguity.
Another convention reserves one slot: for a physical array of length , empty means and full means . Its usable capacity is , and now recovers its count because . A five-slot array then stores at most four items.
This is the convention of the kernel's documented CIRC_* helpers, which additionally require power-of-two array lengths. It is not a universal property of circular buffers; our capacity-five count-based example uses all five slots and does not use those helpers.[ref]
The smallest count-based case, , makes the issue vivid: both and are always zero, but still distinguishes one live item from none. Capacity zero is excluded from our formulas because remainder modulo zero is undefined.
Circular buffer indices Created 2026-10-18 Updated 2026-10-24
Let the capacity be a positive integer , and allocate exactly array slots . Store the front array index and count . The representation invariant is
The logical sequence and next-write array index are
Here denotes the nonnegative remainder from modular arithmetic. The count determines which slots are live: leftover bits in other slots are not members of the FIFO queue. If , has no current front item; it identifies the position where the next successful insertion begins. If , points at a live item, so writing there requires an explicit overwrite policy.
For , enqueue(x) writes , then increments , leaving fixed. For , dequeue() saves and returns , then sets and . These are sequential updates, completed before the next call. A rejecting interface tests or first and returns full or empty, respectively, without changing state. peek returns only when ; size returns .
One can instead store and , recovering . On insertion, advance and increment ; on removal, keep fixed and decrement . Thus the next-write array index is not necessarily the front array index.
Names such as head and tail require checking the actual API: the kernel documentation calls the insertion position head and the removal position tail.[ref]
Figure 1.
A wrapped circular buffer has one logical sequence across the end of its physical array
. Capacity five, front , count , and next-write index . Follow physical slots to read . Slot 1 still shows the removed label , but it is outside the live sequence. Details.
The diagram is the state after step 8 of circular buffer operation trace. Reading the physical array from slot zero would give , which includes a removed label and puts first. Reading from for exactly items gives . The next insertion uses slot , not slot . This distinction also explains why the live sequence may need two contiguous pieces of the array: here slots through , followed by slot . No items need to move to make this wrap work.
With storage allocated in advance, each successful insertion, removal, peek, size, and full/empty check uses worst-case time complexity in Big O notation. This assumes constant-cost indexed array access, bounded machine word index arithmetic and fixed-cost scalar item reads and writes. Each operation touches a bounded number of slots and fields regardless of and ; no shifting, scanning or resizing occurs. Rejected operations also take constant work. Producing a copy of all live items takes work under the same scalar-cost assumptions.
The preallocated array occupies scalar slots plus constant bookkeeping. Allocation and any initial slot construction are separate setup costs. Copying a large item, running an expensive destructor or callback, allocating an item, or growing a dynamic array changes the operation cost and must be charged separately. The bound is worst-case structural work, with no need for amortized analysis, and says nothing about a measured wall-clock deadline.
These formulas describe sequential calls. Wrapping array indices implies neither atomic operations nor safe access from multiple software threads. Publication of an item's contents and reuse of its slot require a protocol justified under the relevant memory model, including any necessary acquire-release synchronization. The kernel documentation discusses barriers under explicit producer/consumer restrictions; Boost's documentation separately requires mutual exclusion for shared access involving writes. Neither property follows merely from choosing a circular buffer.[ref][ref]
David Howells Created 2026-10-18 Updated 2026-11-05
David Howells is a software engineer who has contributed technical documentation for the Linux kernel. With Paul E. McKenney, he authored a guide to circular buffers, including the interaction between index arithmetic and synchronization.[ref]
Their example separates the producer's insertion index from the consumer's removal index. With one producer and one consumer, memory barriers publish initialized data before an index advertises it as available. This illustrates why a correct data structure needs both an indexing convention and an account of what concurrent operations can observe.[ref]
Paul E. McKenney Created 2026-10-18 Updated 2026-11-05
Paul E. McKenney is a computer scientist known for work on concurrent computing and operating-system kernels. His contributions include read-copy-update, developed for systems in which reads are frequent and updates must coexist with them.[ref]
The key read-copy-update distinction is between removing an object from a shared data structure and reclaiming its storage. An earlier reader may still hold a reference after removal; reclamation waits until the relevant readers have finished. That separation permits lighter synchronization on the read path. It is an example of how the lifetime of a shared object becomes part of a concurrent algorithm.[ref]
With David Howells, he also authored the kernel guide to circular buffers and their memory barriers.[ref]