40 questions with answers for candidates with 5 years of experience — 2 easy, 26 medium, 12 hard.
What interviewers expect: 5 years experience
At five years, interviews include system design, advanced concurrency, memory ordering, template techniques and debugging of complex crashes or latency problems. Interviewers look for technical leadership: reviewing designs, setting coding standards, mentoring juniors and driving modernisation. Expect deep follow-up questions on any project you mention, including numbers, alternatives considered and lessons learned.
This set is weighted towards Pointers and Memory Management (9), Multithreading and Concurrency (7), Standard Template Library (STL) (6), Templates and Generic Programming (6).
System design Advanced concurrency and atomics Performance engineering Mentoring and code quality
Solved at this levelStart practicing
3 QUESTIONS FREESign up to unlock the full question bank and track your progress.Unlock every question and answer, then continue practising company-wise and package-wise.
✓ 3 questions free • Unlock the rest in seconds
01
A program does millions of lookups in a std::map with string keys and is slow. How would you speed it up?
If ordering is not needed, switch to std::unordered_map and call reserve with the expected size to avoid rehashing. For string keys, use heterogeneous lookup with string_view to avoid building temporary strings. If keys are fixed after loading, a sorted vector with binary search is very cache friendly. I would profile before and after, because hashing long strings has its own cost.
02
What is the difference between push_back and emplace_back?
push_back takes an existing object, copying or moving it into the container. emplace_back forwards constructor arguments and builds the element directly in place, which can avoid a temporary. For example v.emplace_back(name, age) constructs a Person inside the vector. The gain is small when you already have an object, and emplace_back can call explicit constructors unexpectedly, so use it deliberately.
03
A generic average function must accept only numeric types. How would you enforce that?
In C++20 I would constrain it: template <typename T> requires std::is_arithmetic_v<T> double average(const std::vector<T>& v). Passing a vector of strings then fails with a clear constraint error. On C++17 the same check can go in a static_assert or std::enable_if. I would also decide whether bool and char should be allowed, since they are technically arithmetic types.
04
Describe a situation where you disagreed with a senior engineer about a design decision.
Situation: a senior wanted a deep inheritance hierarchy for device drivers. Task: I believed composition would be simpler to test. Action: instead of arguing in the meeting, I built a small prototype of both designs, measured test setup effort and shared the results calmly, while accepting that the final call was theirs. Result: we chose a hybrid with a thin interface plus composition, and the senior later asked me to document it for the team.
05
Write a variadic function that sums any number of arguments and one that prints them.
I start by measuring, not guessing. I define a target, such as p99 under 50 ms, create a repeatable benchmark, and profile with perf to find where time actually goes. Then I fix the biggest hotspot first, usually algorithmic complexity, unnecessary copies, allocations or I/O, and re-measure after each change. I keep correctness tests running and stop when the target is met.
07
Use a C++20 ranges pipeline to get squares of the first three even numbers.
#include <iostream>
#include <ranges>
#include <vector>
int main() {
std::vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8};
auto result = v
| std::views::filter([](int x) { return x % 2 == 0; })
| std::views::transform([](int x) { return x * x; })
| std::views::take(3);
for (int x : result) std::cout << x << ' '; // 4 16 36
}
// Views are lazy: elements are processed only while iterating.
A variadic template accepts any number of template arguments using a parameter pack, written template <typename... Args>. Before C++17 packs were processed recursively. Fold expressions from C++17 apply an operator across the whole pack in one line, for example return (args + ...); sums all arguments. They power std::make_unique, std::tuple, std::format and logging functions that take arbitrary arguments.
09
Show the copy-and-swap idiom and explain why it gives strong exception safety.
#include <future>
#include <numeric>
#include <vector>
long long parallelSum(const std::vector<int>& v, int parts = 4) {
std::vector<std::future<long long>> futs;
std::size_t chunk = v.size() / parts;
for (int i = 0; i < parts; ++i) {
auto b = v.begin() + i * chunk;
auto e = (i == parts - 1) ? v.end() : b + chunk;
futs.push_back(std::async(std::launch::async, [b, e] {
return std::accumulate(b, e, 0LL);
}));
}
long long total = 0;
for (auto& f : futs) total += f.get();
return total;
}
// C++17 alternative: std::reduce(std::execution::par, v.begin(), v.end(), 0LL)
11
Why should you prefer make_unique and make_shared over calling new directly?
They avoid writing raw new, so no allocated object can leak if another expression in the same call throws. make_shared also allocates the object and the control block together in one allocation, which is faster and more cache friendly. One trade-off: with make_shared the memory block stays allocated until every weak_ptr is gone, which matters for very large objects.
12
Use std::variant and std::visit to handle different message types.
Situation: a pricing job took 40 minutes because it searched a vector of 2 lakh products linearly for every order line. Task: bring it under 5 minutes without changing output. Action: I profiled with perf, confirmed the hot loop, replaced the search with an unordered_map keyed by product ID built once with reserve, and added a regression test. Result: the job finished in about 3 minutes and the team adopted profiling before optimising.
Resource Acquisition Is Initialization ties a resource's lifetime to an object's lifetime. The constructor acquires the resource, such as memory, a file, a socket or a mutex, and the destructor releases it. Because destructors run automatically when a scope ends, even when an exception is thrown, resources are never leaked. std::unique_ptr, std::lock_guard and std::fstream are standard RAII types.
15
What is the diamond problem and how does virtual inheritance solve it?
When B and C both inherit from A and D inherits from B and C, D contains two copies of A, so accessing A's members is ambiguous. Declaring class B : virtual public A and class C : virtual public A makes D share a single A subobject. The most-derived class then initialises A directly. Virtual inheritance adds pointer overhead, so it is used sparingly, usually for interface-like bases.
Most compilers give each polymorphic class a vtable, a static table of function pointers for its virtual functions. Every object stores a hidden vptr pointing to its class's vtable. A call through a base pointer loads the vptr, looks up the slot and jumps to the right override. The cost is one pointer per object, an indirect call and usually no inlining, which matters only in very hot loops.
17
What is a custom deleter for a smart pointer and when do you need one?
A custom deleter tells the smart pointer how to release a resource that was not created with plain new. For example std::unique_ptr<FILE, decltype(&fclose)> f(fopen(path, mode), &fclose); closes the file automatically. Deleters are used for C library handles, sockets, OS handles and objects from custom pools. For unique_ptr the deleter is part of the type, while shared_ptr stores it in the control block.
18
A low-latency system needs a queue between two threads with no locks. How would you approach it?
For one producer and one consumer, a single-producer single-consumer ring buffer is the standard answer: a fixed-size array with atomic head and tail indices, using release on publish and acquire on read, and padding so the indices sit on different cache lines. It avoids allocation and syscalls. For multiple producers I would use a proven library rather than writing my own, and verify with stress tests and TSan.
19
You parallelised a loop across eight threads but it became slower than the single-threaded version. What could be wrong?
Likely causes are lock contention when all threads share one mutex, false sharing when per-thread counters sit on the same 64-byte cache line, too little work per task so thread creation dominates, or memory bandwidth limits. I would profile with perf, pad per-thread data with alignas(64) or std::hardware_destructive_interference_size, use thread-local accumulation and a thread pool, and make chunks larger.
20
Describe a time you improved code quality by changing how memory was managed.
Situation: our module used raw new and delete everywhere and QA reported frequent leaks. Task: make it safe without a full rewrite. Action: I proposed a step-by-step migration to unique_ptr and std::vector, wrote a one-page ownership guideline and enabled LeakSanitizer in CI so new leaks failed the build. Result: leak reports dropped to zero within two sprints and reviews became faster because ownership was obvious.
21
A binary runs on your laptop but fails on the server with error while loading shared libraries. What is wrong?
The program depends on a shared library that is missing or in a different version on the server, or the loader cannot find it. I would run ldd on the binary to see unresolved libraries, then install the right package, set the rpath at link time, or ship the library alongside. A newer glibc or libstdc++ on the build machine is a common cause; build inside a container matching production, or link statically where allowed.
delete destroys a single object created with new. delete[] destroys an array created with new[] and calls the destructor of every element, using an element count the runtime stores with the allocation. Using delete on an array or delete[] on a single object is undefined behaviour and can leak or corrupt the heap. In modern code std::vector or std::unique_ptr<T[]> removes the need for either.
Inside a template, a name that depends on a template parameter, such as T::value_type, could be a type or a static member, so the compiler assumes it is a value. You must write typename T::value_type x; to say it is a type. Similarly, a dependent member template needs the template keyword, as in obj.template get<0>(). C++20 relaxes this in some contexts, but the rule still matters.
Placement new constructs an object in memory you already own, written new (buffer) T(args). It does not allocate, so you must call the destructor yourself and release the buffer separately. A memory pool pre-allocates large blocks and hands out fixed-size slots, avoiding slow general-purpose allocation and fragmentation. Game engines, low-latency trading systems and std::pmr allocators use these for predictable performance.
Situation: a multithreaded integration test failed about once in twenty runs and QA kept reopening the ticket. Task: make it reliable without hiding a real bug. Action: I sat with the QA engineer, ran the test in a loop with logging, and saw it used sleep to wait for a worker. I replaced the sleep with a condition variable signal and fixed a small real race we found. Result: 1000 consecutive passes and better trust between dev and QA.
29
Write a deadlock-free money transfer between two bank accounts.
std::optional<T> holds either a T or nothing, ideal for functions that may not return a value, without using magic values or pointers. std::variant<A, B> is a type-safe union that holds exactly one of the listed types, inspected with std::visit or std::get_if. std::any holds a value of any copyable type and needs any_cast to read it. Prefer optional and variant because they are checked at compile time.
31
You need to build an LRU cache with O(1) get and put. Which containers would you use?
Combine a doubly linked list with a hash map. std::list stores key-value pairs in usage order, with the most recent at the front. std::unordered_map maps each key to its list iterator. On get, splice the node to the front in O(1). On put, update or insert at the front and, if over capacity, remove the back node and its map entry. list iterators stay valid after splice, which makes this work.
Ranges let algorithms take whole containers, as in std::ranges::sort(v), instead of begin and end pairs. Views such as filter, transform, take and reverse are lazy and compose with the pipe operator, for example v | std::views::filter(isEven) | std::views::transform(square). Nothing is computed until you iterate. Ranges also use concepts, so errors are clearer, and projections let you sort by a member easily.
33
Two objects hold shared_ptr references to each other and their memory is never released. Why, and how do you fix it?
This is a reference cycle. Each object keeps the other's reference count above zero, so neither destructor runs even after all outside pointers are gone. Fix it by making one direction non-owning, usually the back pointer, with weak_ptr. For example, a parent holds shared_ptr to its children, while each child holds a weak_ptr to the parent and calls lock() when it needs access.
34
What does C++20 std::jthread add over std::thread?
std::jthread joins automatically in its destructor, so forgetting join no longer calls std::terminate. It also supports cooperative cancellation: the thread function can take a std::stop_token, check stop_requested() in its loop, and the owner calls request_stop(), which the destructor also does before joining. std::stop_callback lets you register cleanup actions. This makes long-running worker threads safer to write.
35
What are type traits and how does if constexpr use them?
Type traits in <type_traits> answer questions about types at compile time, such as std::is_integral_v<T>, std::is_pointer_v<T> or std::is_same_v<T, U>, and transform types, such as std::remove_reference_t. if constexpr picks a branch at compile time and discards the other, so code like if constexpr (std::is_integral_v<T>) can call integer-only operations safely inside a generic function.
unique_ptr has sole ownership, cannot be copied, can only be moved, and costs almost nothing over a raw pointer. shared_ptr keeps a reference count in a control block and destroys the object when the last owner disappears. weak_ptr observes an object owned by shared_ptr without increasing the count; call lock() to get a shared_ptr if it still exists. weak_ptr breaks cycles and suits caches.
const means a value cannot be modified after initialisation, but it may still be computed at run time. constexpr means the variable or function can be evaluated at compile time when its inputs are constant. With constexpr int sq(int x) { return x * x; } you can write int arr[sq(4)];. Use constexpr for array sizes, template arguments and lookup tables, and consteval when evaluation must happen at compile time.
39
What is the erase-remove idiom and what replaced it in C++20?
std::remove does not delete elements; it shifts the kept elements forward and returns an iterator to the new logical end. You then call erase to shrink the container: v.erase(std::remove(v.begin(), v.end(), 0), v.end());. remove_if works the same with a predicate. C++20 adds std::erase(v, value) and std::erase_if(v, pred), which do both steps in one call.
40
What is template argument deduction and class template argument deduction?
For function templates the compiler deduces T from the call, so maxOf(3, 7) deduces int; mixing int and double fails because T would be ambiguous. Class template argument deduction, added in C++17, lets you write std::vector v{1, 2, 3}; or std::pair p(1, 2.5); without spelling the template arguments. Deduction guides can be written to tell the compiler how to deduce types from constructors.
Frequently asked questions
What is asked in C++ interviews for 5 years of experience?
At five years, interviews include system design, advanced concurrency, memory ordering, template techniques and debugging of complex crashes or latency problems. Interviewers look for technical leadership: reviewing designs, setting coding standards, mentoring juniors and driving modernisation. Expect deep follow-up questions on any project you mention, including numbers, alternatives considered and lessons learned.
How many questions should I practise before a C++ interview?
Work through all 40 questions here, then the company-wise sets for the employers you are targeting. Quality matters more than count: be able to explain why each answer is right.
Which topics matter most for 5 years of experience?
Focus first on System design, Advanced concurrency and atomics, Performance engineering, Mentoring and code quality.