C++C++ Explainer
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
getO(1)
putO(1)
evictO(1)list back
spaceO(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_; }
};
shared_ptr<T>ptr, ctrlcontrol block · ref=2T on heap
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?