C++ · 7 · Advanced / Job-Ready36 / 36 · 100%
Interview Design Problems
Three implementations that come up again and again.
Examples: LRU cache, mini shared_ptr, thread pool
shortcuts: ← prev · → next · M mark
1
LRU Cache — O(1)
Hash map of keys to list iterators, plus a list ordered by recency.
Example
example
class LRUCache {
using Pair = std::pair<int,int>;
std::list<Pair> items_; // front = newest
std::unordered_map<int, std::list<Pair>::iterator> map_;
std::size_t cap_;
public:
explicit LRUCache(std::size_t cap) : cap_(cap) {}
std::optional<int> get(int key) {
auto it = map_.find(key);
if (it == map_.end()) return std::nullopt;
items_.splice(items_.begin(), items_, it->second); // O(1) move to front
return it->second->second;
}
void put(int key, int value) {
if (auto it = map_.find(key); it != map_.end()) {
it->second->second = value;
items_.splice(items_.begin(), items_, it->second);
return;
}
if (map_.size() == cap_) {
map_.erase(items_.back().first);
items_.pop_back();
}
items_.emplace_front(key, value);
map_[key] = items_.begin();
}
};Complexity
| get | O(1) | |
| put | O(1) | |
| evict | O(1) | list back |
| space | O(capacity) |
2
A Mini shared_ptr
Reference counting, atomically.
Example
example
template <typename T>
class SharedPtr {
T* ptr_ = nullptr;
std::atomic<int>* count_ = nullptr;
public:
explicit SharedPtr(T* p) : ptr_(p), count_(new std::atomic<int>(1)) {}
SharedPtr(const SharedPtr& o) : ptr_(o.ptr_), count_(o.count_) {
if (count_) count_->fetch_add(1, std::memory_order_relaxed);
}
~SharedPtr() {
if (count_ && count_->fetch_sub(1, std::memory_order_acq_rel) == 1) {
delete ptr_;
delete count_;
}
}
T& operator*() const { return *ptr_; }
T* operator->() const { return ptr_; }
};two owners, one control block
WATCH OUT
The control block must be atomic; the pointee is NOT thread-safe. And this toy version still needs assignment operators and weak-ref support.
3
A Thread Pool
N workers pulling from one synchronized queue.
Example
example
class ThreadPool {
std::vector<std::jthread> workers_;
std::queue<std::function<void()>> tasks_;
std::mutex m_; std::condition_variable cv_; bool stop_ = false;
public:
explicit ThreadPool(unsigned n = std::thread::hardware_concurrency()) {
for (unsigned i = 0; i < n; ++i)
workers_.emplace_back([this]{
for (;;) {
std::function<void()> job;
{
std::unique_lock lk(m_);
cv_.wait(lk, [this]{ return stop_ || !tasks_.empty(); });
if (stop_ && tasks_.empty()) return;
job = std::move(tasks_.front()); tasks_.pop();
}
job();
}
});
}
void submit(std::function<void()> job) {
{ std::lock_guard lk(m_); tasks_.push(std::move(job)); }
cv_.notify_one();
}
~ThreadPool() {
{ std::lock_guard lk(m_); stop_ = true; }
cv_.notify_all(); // jthread members join automatically
}
};4
How to Answer
Interviewers grade the reasoning, not just the code.
Interview question
What do interviewers look for in these problems?
Quick check
Why does the LRU cache use std::list rather than std::vector?