Why this matters: the ring buffer looks like the smallest problem in the systems set — two indices and an array. That smallness is the trap. Every requirement answer moves the design somewhere different, and the single most important question is one many candidates never ask: how many threads touch this thing? Miss it, and you'll design confidently for the wrong universe.
The prompt, as given
Implement a ring buffer — a fixed-capacity circular queue that carries records from a producer to a consumer.
It sits on the hot path between two parts of a system, so latency matters: every nanosecond you spend inside push or pop is paid on every record. The capacity is fixed up front, and when the buffer is full, that fact has to surface somehow rather than silently losing data.
Notice what the prompt commits to: fixed capacity, a hot path, no silent loss. Notice what it deliberately doesn't say: how many producers and consumers, what a record is, what the API even looks like. Those blanks are the first act.
The questions, and why each one matters
"How many producer threads, and how many consumer threads?" This is the question the whole problem pivots on, and asking it first signals that you already know why: a queue touched by one thread is a trivial exercise; a queue shared between threads is a memory-ordering problem. The answer shapes literally every line you'll write. Everything else is detail by comparison.
"When the buffer is full, what happens?" The prompt promised no silent loss, but that still leaves choices: reject the push and tell the caller, or block until space appears. Rejection keeps the buffer's own code non-blocking; blocking pushes a waiting policy inside. You want the interviewer's pick before designing the API.
"What is a record — size, ownership?" Fixed-size plain records mean you can copy bytes into preallocated slots and never allocate. Variable sizes or owned resources drag in lifetime questions that change the storage story completely.
"May I require the capacity to be a power of two?" A small question that carries a signal: wrapping an index costs a modulo in the general case, but if capacity is a power of two, wrap becomes a bitwise AND with a mask. On a hot path where every operation pays the cost, asking for this constraint — and knowing why you want it — reads as exactly the instinct the round is probing for.
"Should push and pop ever block?" Blocking couples the buffer to a waiting strategy (and to the OS). A non-blocking try_push/try_pop API keeps the buffer minimal and pushes the wait-or-retry decision to callers, who know their own latency budgets. Ask, don't assume.
"What's the actual use case?" Not idle curiosity — "network-receive thread handing packets to a processing thread" tells you the traffic shape (bursty), the tolerance (occasional rejected pushes are fine), and the metric that matters (per-operation latency, not throughput fairness).
The requirement set this chapter builds against
Threads exactly ONE producer thread and ONE consumer thread
(SPSC), running in parallel on different cores
Full buffer try_push returns false — rejection, never silent loss,
never blocking
Records fixed-size POD, 64 bytes; copied in, copied out;
no ownership concerns
Capacity fixed at construction; power of two allowed (we take it)
API non-blocking try_push / try_pop only; what a caller
does on false is the caller's problem
Use case NIC-receive thread -> processing thread; latency first
Out of scope overwrite-oldest variants, blocking APIs
(lesson 04 visits both)
The SPSC contract is the load-bearing line. One producer means only one thread ever advances the write position; one consumer means only one thread ever advances the read position. That asymmetry — each index has exactly one writer — is what the entire design of the next lesson stands on.
As with every chapter in this course: this is a representative requirement set. A live interviewer may hand you different answers — blocking semantics, bigger records, a different full-buffer policy. The skill is the conversation that surfaces the contract, not this particular contract.
Key takeaway
The ring buffer's requirements conversation has one question that towers over the rest: how many threads on each side? This chapter's contract is SPSC — one producer, one consumer, non-blocking try_push/try_pop, fixed-size records, rejection on full, power-of-two capacity. Every invariant in the coming design is true because of that contract, which is why you state it out loud before designing anything.