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.
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
- 01TSQueue design — the #1 asked C++ design question
- 02push — blocking with not_full_ condition variable
- 03pop — blocking with not_empty_ condition variable
- 04Non-blocking API — try_push and try_pop
- 05const correctness — mutable mutex
- 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.