Asked at Flow Traders, Chicago Trading Company
Theory: Concurrency From ScratchRead the problem, hints and solution here. The editor needs a bigger screen: open this page on a laptop to write and run your code.
The bounded buffer is the interview classic of thread coordination for a
reason: the correct solution is fifteen lines, and there are at least
four famous ways to write fifteen subtly wrong ones (the lost wakeup, the
if instead of while, the notify aimed at the wrong side, the shutdown
that strands a sleeping consumer). Desks use it as the fastest possible
probe of whether you have internalised the condition-variable discipline
or just seen it once.
Implement BoundedBuffer, a fixed-capacity FIFO of long long for
concurrent producers and consumers:
explicit BoundedBuffer(std::size_t capacity): at most capacity
buffered elements. capacity >= 1.bool put(long long value): blocks while the buffer is full and
not closed. Returns true once enqueued; returns false (without
enqueuing) if the buffer is closed, including when close() arrives
while blocked.std::optional<long long> take(): blocks while the buffer is empty
and not closed. Returns the oldest element; after close(), keeps
returning buffered elements until the buffer is drained, then returns
std::nullopt to every caller, forever.void close(): idempotent. Wakes all blocked producers and
consumers so they can observe the shutdown.Discipline requirements, all graded in interviews even when a judge cannot see them:
wait() guarded by an if is wrong on every platform: spurious
wakeups are permitted by the standard, and a woken thread may find
another thread took its element first.sleep_for
polling, no yield loops. Blocked threads must consume no CPU.[]{
BoundedBuffer buffer(16);
std::thread producer([&buffer] {
for (long long i = 0; i < 100000; ++i) buffer.put(i);
buffer.close();
});
long long count = 0, violations = 0, next = 0;
while (auto v = buffer.take()) {
if (*v != next) ++violations;
++next;
++count;
}
producer.join();
return std::pair<long long, long long>(count, violations);
}()
// (100000, 0) (100k values squeezed through 16 slots, in exact order)
[]{ BoundedBuffer buffer(8); std::vector<long long> out; out.push_back(buffer.put(5)); out.push_back(buffer.put(6)); out.push_back(buffer.put(7)); buffer.close(); out.push_back(buffer.put(8)); out.push_back(buffer.take().value_or(-1)); out.push_back(buffer.take().value_or(-1)); out.push_back(buffer.take().value_or(-1)); out.push_back(buffer.take().has_value()); return out; }()
// [1, 1, 1, 0, 5, 6, 7, 0] (close rejects new puts but drains what was accepted)
take() blocked on an empty buffer when close() fires must wake
and return std::nullopt, not sleep forever: fold the closed flag into
the wait predicate.if-instead-of-while that this
scheduler happened to forgive; the editorial covers the bugs the judge
cannot see, and ThreadSanitizer plus code review is how they are caught
in practice.Run your code to check it against the sample tests. Results appear here.