Interview conceptSoftware Engineering Fundamentals

Concurrent Queues, Ring Buffers, And Lock-Free Synchronization

Asked of: Software Engineer

Last updated

Editorial infographic showing an 8-slot lock-free ring buffer, producer and consumer positions, per-slot sequence states, release/acquire publication, and cache-line separation.

What's being tested

This topic tests whether you can reason about concurrent data structures rather than merely add a mutex around shared state. The interviewer is probing atomicity, ownership of memory locations, progress guarantees, memory ordering, bounded-capacity behavior, and correctness under contention. For a Software Engineer at Citadel, these skills matter because market-data and task-processing paths often demand both predictable latency and exact behavior when updates, consumers, and failures occur simultaneously.

Core knowledge

  • A ring buffer stores elements in fixed-capacity storage, using indices modulo capacity: index = position % capacity. It avoids allocation and provides predictable memory usage, but must distinguish full from empty using counters, a reserved slot, or sequence numbers.

  • For a single producer, only that thread advances the write position; consumers must atomically claim read positions, commonly with compare_exchange_weak. Claiming before reading prevents two consumers from processing the same element.

  • Publication requires correct memory ordering: the producer writes payload data, then performs a store(..., release) on its publication marker; the consumer performs an acquire load before reading. Relaxed atomics alone do not publish payload writes.

  • A lock-free algorithm guarantees system-wide progress: some thread completes in finitely many steps. Wait-free additionally bounds every individual operation. A mutex may be correct but blocking; lock-free does not mean starvation-free or automatically faster.

  • Sequence counters per slot solve both publication and reuse: a producer waits until a slot’s sequence indicates free, writes the payload, then advances the sequence; a consumer validates readiness before reading and marks the slot reusable afterward.

  • Capacity must account for outstanding work. With monotonic unsigned positions, occupancy is approximately write_position - read_position; ensure counters cannot overflow ambiguously, or use sufficiently wide integers and carefully defined wraparound arithmetic.

  • False sharing occurs when independent atomic counters share a cache line, causing cache-coherence traffic. Place producer and consumer positions on separate cache lines using padding or alignas(64) where the target architecture justifies it.

  • A queue’s progress policy must be explicit: spin, yield, block with condition_variable, or return “full/empty.” Spinning can reduce p99 latency at low contention but wastes CPU and can starve lower-priority work.

  • A conflating queue needs two structures: a map from symbol to its current entry and an ordered container of symbols awaiting delivery. Updating an existing symbol replaces its value without creating a duplicate, while new symbols preserve arrival order.

  • Sentinel values such as -1 are unsafe when negative prices are valid. Use std::optional, a tagged std::variant, or an explicit validity bit; define whether zero, negative values, and deletion are valid domain states.

  • ABA occurs when a location changes from value A to B and back to A, making a compare-and-swap mistake an old state for an unchanged one. Version tags, per-slot sequence numbers, or ownership schemes reduce this risk.

  • Correctness includes destruction and exception behavior. Decide whether objects are constructed in-place, when destructors run, what happens if a consumer exits after claiming an item, and whether producers may overwrite storage containing an unread object.

Worked example

For “Implement a single-producer multi-consumer ring buffer”, I would first clarify whether each item is consumed exactly once, whether operations may block, whether overwriting is allowed, and whether the expected interface is C++-specific. I would state a bounded-capacity assumption, single producer ownership of writes, and that consumers compete to claim each item rather than broadcasting it to every consumer. I would organize the answer around storage and index layout, producer publication, consumer claim/read/release, and full/empty behavior. The producer would reserve or write the next slot, publish it with release semantics, and advance a sequence or write marker. Each consumer would use CAS to claim a unique read position, acquire the corresponding publication state, copy or move the payload, and make the slot reusable. The explicit tradeoff is between a simple CAS-based shared read index and per-slot sequence numbers: sequence numbers handle wraparound and producer/consumer visibility more robustly at the cost of metadata and complexity. I would mention cache-line padding and bounded spinning as latency considerations rather than hiding them inside correctness logic. If I had more time, I would add stress tests with randomized consumer scheduling, wraparound tests, ThreadSanitizer runs, and benchmarks for contention and p99 latency.

A second angle

For “Conflating Quote Queue That Keeps Only the Latest Price per Symbol”, the central issue shifts from claiming slots safely to defining update semantics under concurrency. A strong design uses a symbol-to-entry map plus an ordered queue, carefully marking whether a symbol is already enqueued so repeated updates do not create duplicate work. The candidate must clarify whether a consumer may observe an intermediate price and whether ordering means first arrival or most recent update. A mutex may be the best initial implementation; lock-free replacement is only worthwhile after measuring contention and latency.

Common pitfalls

Pitfall: Treating volatile as synchronization is incorrect; it may inhibit some compiler optimizations but does not provide atomicity, inter-thread visibility, or ordering. Use std::atomic and explain the happens-before relationship.

Pitfall: Saying “the ring buffer is lock-free because it uses atomics” skips the hard parts: multi-consumer index claiming, slot reuse, wraparound, and payload publication. Show who owns each mutation and what prevents overwrite or duplicate consumption.

Pitfall: Jumping directly to a complex lock-free algorithm without clarifying semantics signals poor engineering judgment. Start with a correct mutex-based or single-owner design, state its bottleneck, then justify each atomic optimization and its testing plan.

Connections

The interviewer may pivot to linearizability, memory reclamation such as hazard pointers or epoch-based reclamation, and backpressure under overload. They may also connect the queue to Kafka delivery semantics, priority queues, deadlock avoidance, or cache-coherence effects.

Further reading

Practice questions

Related concepts