🧰Go deeper — read the bookImplement it yourself: the interview classics— runnable code & full walkthrough →

coding problems · high

Design: Thread-Safe Queue — mutex, condition_variable, bounded, move semantics

Thread-Safe Queue — the most asked C++ design question. Structure: mutable mutex, not_empty_ + not_full_ condition variables, deque<T> backing. push(T val): wait(not_full_, bounded check); push_back(move(val)); notify_one(not_empty_). pop(): wait(not_empty_, !empty check); move front; pop_front; notify_one(not_full_); return. try_push / try_pop: lock_guard (no wait); return false/nullopt if full/empty. mutable mutex: const size()/empty() can lock; mutex is not part of logical const state. Use move semantics throughout — pass T by value in push, return T by value from pop. Shutdown: atomic<bool> stopped; check in wait predicate; notify_all on shutdown. Variants: priority queue (replace deque with priority_queue), lock-free (Michael-Scott), bounded SPSC (simpler ring buffer). In production: tbb::concurrent_queue, folly::MPMCQueue, Boost.Lockfree.

🔑 Key line

Thread-Safe Queue: mutex + not_empty_/not_full_ CVs + deque backing; push: wait(not_full) → push_back(move(val)) → notify_one(not_empty); pop: wait(not_empty) → move(front) → pop_front → notify_one(not_full); mutable mutex for const size/empty; try_push/try_pop for non-blocking API.

The code

// Thread-Safe Queue — most asked C++ design question
// Encapsulates: mutex, condition_variable, bounded/unbounded queue
template <typename T>
class TSQueue {
mutable mutex mtx_;
condition_variable not_empty_;
condition_variable not_full_;
deque<T> q_;
size_t capacity_; // 0 = unbounded
public:
explicit TSQueue(size_t cap = 0) : capacity_(cap) {}
// Blocking push — waits if full
void push(T val) {
unique_lock<mutex> lk(mtx_);
not_full_.wait(lk, [this] { return capacity_ == 0 || q_.size() < capacity_; });
q_.push_back(move(val));
not_empty_.notify_one();
}
// Non-blocking push — returns false if full
bool try_push(T val) {
lock_guard<mutex> lk(mtx_);
if (capacity_ && q_.size() >= capacity_)
return false;
q_.push_back(move(val));
not_empty_.notify_one();
return true;
}
// Blocking pop — waits until element available
T pop() {
unique_lock<mutex> lk(mtx_);
not_empty_.wait(lk, [this] { return !q_.empty(); });
T val = move(q_.front());
q_.pop_front();
not_full_.notify_one();
return val;
}
// Non-blocking pop — returns nullopt if empty
optional<T> try_pop() {
lock_guard<mutex> lk(mtx_);
if (q_.empty())
return nullopt;
T val = move(q_.front());
q_.pop_front();
not_full_.notify_one();
return val;
}
// Drain — pop all remaining items (for shutdown)
void drain(vector<T>& out) {
lock_guard<mutex> lk(mtx_);
while (!q_.empty()) {
out.push_back(move(q_.front()));
q_.pop_front();
}
}
size_t size() const {
lock_guard<mutex> lk(mtx_);
return q_.size();
}
bool empty() const {
lock_guard<mutex> lk(mtx_);
return q_.empty();
}
};

What this lesson walks through

  1. 01TSQueue design — the #1 asked C++ design question
  2. 02push — blocking with not_full_ condition variable
  3. 03pop — blocking with not_empty_ condition variable
  4. 04Non-blocking API — try_push and try_pop
  5. 05const correctness — mutable mutex
  6. 06Interview follow-ups — scaling and variants

A thread-safe queue is the most asked C++ design question at FAANG and HFT firms. The interviewer wants to see: mutex protection, condition variable for blocking wait, move semantics for efficiency, both blocking and non-blocking APIs, and optionally a bounded variant. This design combines everything from RAII, move semantics, and concurrency into one clean class.

See it animated — step by step, at your own pace

Unlock the full interactive walkthrough of Design: Thread-Safe Queue — mutex, condition_variable, bounded, move semantics and 100+ animated C++ interview lessons.

← Previous
LeetCode: 8 Must-Know Patterns — Two Pointers, Sliding Window, BFS, DP, Monotonic Stack
Next →
LeetCode Top Problems C++ — Two Sum, Reverse LL, Floyd, Kadane, BinSearch, BFS