40 questions with answers for candidates with 2 years of experience — 6 easy, 34 medium.
What interviewers expect: 2 years experience
At two years, expect deeper questions on move semantics, templates, iterator invalidation and basic multithreading, along with medium to hard coding problems. Interviewers want ownership stories: a feature you built end to end, a performance or memory issue you diagnosed, and how you handled production bugs. Low-level design questions such as a parking lot or LRU cache appear often.
This set is weighted towards Performance, Debugging and Build Tools (7), Standard Template Library (STL) (7), Pointers and Memory Management (6), Multithreading and Concurrency (5).
Move semantics and templates Basic concurrency Low-level design Production debugging stories
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.
A deadlock happens when threads wait forever for each other's locks, for example thread A holds m1 and waits for m2 while thread B holds m2 and waits for m1. It needs mutual exclusion, hold and wait, no preemption and circular wait. Prevent it by always locking in a fixed global order, locking several mutexes together with std::scoped_lock, keeping critical sections small and never calling unknown code while holding a lock.
02
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.
03
What are std::optional, std::variant and std::any?
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.
Any insertion that causes reallocation, when size exceeds capacity, invalidates all iterators, pointers and references. Insertion without reallocation invalidates those at or after the insertion point. erase invalidates iterators at and after the erased element. clear and resize to a larger size beyond capacity invalidate everything. Holding a pointer to an element and then calling push_back is a classic bug.
05
A teammate added using namespace std; to a shared header file. What problems can this cause?
Every file that includes the header now has all std names pulled into the global namespace. This can cause ambiguous calls or silently pick the wrong function when user code defines names like count, distance, min or data. The problem spreads to every dependent file and is hard to trace. I would remove it and use explicit std:: qualification or narrow using declarations inside .cpp files or function bodies.
std::async runs a function, possibly on another thread, and returns a std::future that will hold its result or exception; calling get() waits for it. Use std::launch::async to force a new thread. std::promise is the writing end: one thread calls set_value or set_exception, and the paired future receives it. They make simple fork-join tasks easier than manual threads, but are not a full thread pool.
A leak happens when heap memory is allocated but never freed and the last pointer to it is lost, so memory use keeps growing. In long-running servers this eventually exhausts RAM. Detect leaks with Valgrind memcheck, LeakSanitizer through -fsanitize=address, or heap profilers. Prevent them with RAII types such as std::unique_ptr and std::vector, which release memory automatically.
08
Which profiling tools do you use for C++ and what does each show?
On Linux, perf samples the CPU and shows hot functions and call graphs, often visualised as flame graphs. Valgrind callgrind counts instructions precisely but runs slowly, and massif or heaptrack profile memory. gprof is older and needs instrumented builds. Intel VTune and AMD uProf show cache misses and pipeline stalls. On Windows, the Visual Studio profiler covers CPU and memory.
09
Should a Car class inherit from Engine or contain an Engine? Explain your choice.
A car is not an engine, it has an engine, so composition is correct: class Car holds an Engine member or a unique_ptr<Engine>. Inheritance models an is-a relationship and exposes the base interface, which would let callers treat a Car as an Engine. Composition also allows swapping engine types at run time through an interface such as PetrolEngine or ElectricMotor and keeps coupling low.
Concepts are named compile-time predicates on template parameters. You can write template <std::integral T> T gcd(T a, T b) or define your own with a requires expression. If a type does not satisfy the concept, the compiler reports a short, readable error at the call site instead of pages of instantiation errors. Concepts also allow overloading by constraint and document the template's expectations.
11
A generic maxOf template gives wrong results when called with two C-style string literals. Why, and how do you fix it?
T is deduced as const char*, so the template compares pointer addresses instead of the characters. The result depends on where the literals are stored. Fix it by adding an overload for const char* that uses std::strcmp, or better, call it with std::string or std::string_view, where operator< compares contents. This shows why generic code must consider what operations mean for each type.
12
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.
A shallow copy copies pointer members, so two objects share one heap buffer; destroying one leaves the other dangling and causes a double free. A deep copy allocates new memory and copies the contents. The compiler-generated copy constructor copies member by member, so a class owning a raw pointer needs a custom copy constructor and copy assignment, or better, should hold a std::vector or unique_ptr instead.
15
Write a generic function template that returns the larger of two values.
template <typename T>
const T& maxOf(const T& a, const T& b) {
return (a < b) ? b : a;
}
// usage
int m = maxOf(3, 7); // T = int
double d = maxOf(2.5, 1.5); // T = double
auto s = maxOf<double>(3, 4.5); // explicit T avoids deduction conflict
16
Your application is slow and you are asked to optimise it. Where do you start?
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.
17
What do compiler optimisation levels like -O0, -O2, -O3 and -Os mean?
-O0 disables optimisation and is best for debugging. -O1 and -O2 enable progressively more optimisations such as inlining, constant propagation and loop optimisations; -O2 is the common release default. -O3 adds aggressive vectorisation and inlining, which may increase code size. -Os optimises for size. Link-time optimisation with -flto optimises across translation units, and -march=native tunes for the current CPU.
18
Write a custom hash so a struct can be used as a key in std::unordered_map.
Exceptions suit rare, truly exceptional failures, keep the happy path clean and work well with RAII, but have unpredictable cost when thrown and are often disabled in embedded or game code. Error codes are explicit and cheap but easy to ignore. C++23 std::expected<T, E> returns either a value or an error, combining explicit handling with clean types. I follow the project convention and use expected for expected failures like parsing.
20
Four threads each increment a shared int one million times, but the final total is less than four million. Why?
counter++ is a read-modify-write of three steps, so threads interleave and overwrite each other's updates, which is a data race and undefined behaviour. Fix it with std::atomic<int> and fetch_add, which is fast for a simple counter, or protect the variable with a mutex. Even better for performance, let each thread count in a local variable and add it to the total once at the end.
21
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.
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.
#include <algorithm>
#include <vector>
void dedupSorted(std::vector<int>& v) {
std::sort(v.begin(), v.end());
v.erase(std::unique(v.begin(), v.end()), v.end());
}
// O(n log n). std::unique only removes adjacent duplicates, so sort first.
// To keep the original order, track seen values in an unordered_set instead.
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.
26
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.
27
What is cache locality and why does it matter for performance?
CPUs load memory in cache lines, usually 64 bytes, and access to L1 cache takes a few cycles while main memory takes hundreds. Code that walks memory sequentially, like iterating a std::vector, uses every byte loaded and lets the prefetcher help. Pointer-heavy structures like linked lists jump around memory and cause cache misses. That is why a vector often beats a list even for insert-heavy workloads.
28
A full build of your C++ project takes 40 minutes. How would you reduce it?
I would measure with Clang -ftime-trace or build tooling to find the slowest files and headers. Then reduce header dependencies using forward declarations and the pimpl idiom, use Ninja for parallel builds, enable ccache or sccache, add precompiled headers for stable heavy headers, and split huge translation units. Unity builds and distributed compilation also help, and CI can cache build outputs.
29
What does the static keyword mean in different contexts?
Inside a function, a static local variable is initialised once and keeps its value between calls. At namespace scope, static gives internal linkage, so the name is visible only in that translation unit. Inside a class, a static data member belongs to the class rather than each object, and a static member function has no this pointer. Since C++17 inline static members can be defined directly in the header.
Adding an integer to a pointer moves it by that many elements, not bytes, so p + 1 on an int* advances by sizeof(int) bytes. Subtracting two pointers into the same array gives the number of elements between them as ptrdiff_t. Arithmetic is only valid inside an array or one past its end; moving beyond that is undefined behaviour. Contiguous iterators work on the same idea.
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.
32
What is function overloading and how does the compiler choose an overload?
Overloading means several functions share a name but differ in the number or types of parameters; return type alone is not enough. The compiler runs overload resolution: exact matches win, then promotions such as char to int, then standard conversions, then user-defined conversions. If two candidates are equally good the call is ambiguous and compilation fails. Name mangling gives every overload a unique linker symbol.
33
Why should we hire you for this C++ developer role?
Situation: I have prepared specifically for systems-level roles. Task: show I can contribute quickly. Action: I have built projects such as a multithreaded log analyser and a small key-value store in modern C++, used CMake, GoogleTest and sanitizers, and practised data structures and debugging with gdb. Result: I can write safe, measurable, well-tested code from the first sprint, and I learn fast from code reviews and feedback.
34
A program crashes with terminate called without an active exception when a function returns. What is the cause?
A std::thread object that is still joinable was destroyed, often because the function returned early or threw before reaching join. The std::thread destructor calls std::terminate in that case. Fix it by making sure every thread is joined on all paths, using an RAII guard, or switching to std::jthread, which joins automatically. Be careful with detach, since detached threads may access destroyed data.
35
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.
36
How do you handle a tight deadline when the C++ code you need to change is complex?
Situation: a client needed a protocol change in two days in code I had not touched. Task: deliver safely. Action: I read the tests first, wrote two new tests for the required behaviour, made the smallest change that passed them, and flagged a risky area to my lead early instead of at the end. I logged a refactoring ticket for later. Result: we shipped on time with no regressions and cleaned up the module the next sprint.
37
Write a program where two threads print odd and even numbers alternately up to 10.
priority_queue is a binary heap that gives O(1) access to the largest element and O(log n) push and pop, but you cannot search or erase arbitrary elements. std::set keeps all elements sorted with O(log n) insert, erase and find, and gives both minimum and maximum. Use priority_queue for schedulers and Dijkstra, and set or multiset when you must update or remove arbitrary items.
39
How would you explain polymorphism to a junior developer who has just joined your team?
Situation: a new joiner was confused by virtual functions in our codebase. Task: help them become productive quickly. Action: I used a real example from our code, a Notifier base class with Email and Sms versions, drew how the vtable decides the call at run time, and gave them a small task to add a WhatsApp notifier. Result: they finished it in a day and later explained the idea to the next intern.
40
Write a C++20 concept and use it to constrain a function.
What is asked in C++ interviews for 2 years of experience?
At two years, expect deeper questions on move semantics, templates, iterator invalidation and basic multithreading, along with medium to hard coding problems. Interviewers want ownership stories: a feature you built end to end, a performance or memory issue you diagnosed, and how you handled production bugs. Low-level design questions such as a parking lot or LRU cache appear often.
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 2 years of experience?
Focus first on Move semantics and templates, Basic concurrency, Low-level design, Production debugging stories.