C++ interviews concentrate on resource management and the costs of each abstraction. Expect questions on RAII and smart pointers, move semantics and why move operations should be noexcept, the Rule of Three, Five and Zero, virtual destructors, STL container trade-offs and iterator invalidation, and the difference between compile-time and runtime polymorphism. Undefined behaviour and the sanitizers used to find it come up regularly. The questions below cover modern C++ practice rather than legacy patterns.
Behavioural Questions
1. Tell me about a C++ project you have worked on. What made it challenging?
Note: C++ is chosen for a reason — performance, hardware access, or an existing codebase. Say which one applied, because it frames everything else about the project.
Cover:
- The domain and why C++. Game engine, embedded firmware, trading system, image processing, or a native library behind another language. The reason matters: "we needed deterministic latency under 100 microseconds" tells the interviewer what kind of engineer you are.
- Which C++ you were writing. C++98 with raw pointers is a different job from modern C++17 with smart pointers and move semantics. Be honest — legacy experience is valuable.
- The hard part. Strong candidates: a memory corruption bug that only appeared in release builds, a data race under concurrency, cutting allocation on a hot path, or long compile times you had to attack.
- How you verified the fix. Sanitizers, Valgrind, a benchmark — this is where C++ interviewers judge rigour.
2. Describe a difficult bug you tracked down in C++. How did you find it?
C++ bugs are distinctive because the symptom is often far from the cause, so the method matters more than the fix.
- The symptom. The classic C++ story is a crash somewhere unrelated to the broken code, or behaviour that changes between debug and release builds, or a failure that disappears when you add a print statement. Any of these points at undefined behaviour.
- The tools. Say which ones and what each found: AddressSanitizer for out-of-bounds access and use-after-free, UndefinedBehaviorSanitizer for signed overflow and bad casts, ThreadSanitizer for data races, Valgrind for leaks, and a debugger with a watchpoint on the corrupted address.
- The root cause. Good ones: a dangling reference to a destroyed temporary, iterator invalidation after a vector reallocated, a missing virtual destructor, a race on a shared variable, or reading past the end of a buffer.
- The systemic fix — turning on sanitizers in CI, or replacing a raw pointer with a smart pointer so the class of bug becomes impossible.
Note: Explaining that undefined behaviour lets the compiler assume the bad case cannot happen — and therefore optimise on that assumption — shows genuine depth.
3. How do you decide between optimising code and keeping it simple?
C++ attracts premature optimisation, so interviewers are checking for discipline.
The default is clarity. Write the straightforward version, measure, and optimise only what the profiler identifies. The reason is not that performance does not matter — it is that intuition about C++ performance is unreliable. Cache behaviour, branch prediction, and compiler optimisation regularly make the "obviously faster" code slower.
When to optimise up front:
- Choice of data structure and algorithm. This is not premature — it is design. Getting the complexity class wrong is expensive to fix later.
- Memory layout on a hot path. Contiguous storage and struct-of-arrays versus array-of-structs are architectural decisions.
- Anything in a documented hard real-time budget.
How to keep it honest: benchmark with realistic data, use a proper harness such as Google Benchmark rather than timing one run, and check the generated assembly when a result surprises you.
Note: A strong story is one where you optimised something, measured no improvement, and reverted it. That is a habit interviewers rarely hear about and always respect.
4. How do you approach code review and maintain quality in a C++ codebase?
C++ gives you many ways to be subtly wrong, so lean on tooling and reserve human attention for design.
Automated:
- clang-format so formatting never appears in review.
- clang-tidy with the modernize, bugprone, and core guideline checks — it catches whole categories of error mechanically.
- Sanitizers in CI, at minimum ASan and UBSan on the test suite. This is the highest-value practice in C++ and many teams still skip it.
- Warnings as errors with
-Wall -Wextra.
What humans should review: ownership and lifetime — who owns this pointer and how long does it live; exception safety; whether the interface is easy to misuse; and const-correctness.
Note: The single most useful review question in C++ is "what happens if this throws halfway through?" It surfaces resource leaks and broken invariants that compile perfectly. Mentioning RAII as the structural answer — that resources should be owned by objects so cleanup is automatic — shows you think in terms of preventing bugs rather than catching them.
5. C++ has evolved rapidly since C++11. How do you keep current and decide what to adopt?
How you keep current: the standards committee papers show what is coming, cppreference is the practical daily reference, and conference talks from CppCon give you the reasoning behind features rather than just the syntax. Most usefully, rewriting something you already wrote using a newer feature teaches you where it does and does not help.
How you decide what to adopt:
- Which standard can you actually use? The compiler your product ships with is the real constraint, particularly in embedded work where the toolchain may be years old.
- Does it remove a class of bug? Smart pointers,
std::optional,std::string_view, range-based for, and structured bindings all do. These are worth adopting immediately. - Does it complicate the code for a marginal gain? Heavy template metaprogramming and clever constexpr can make a codebase unreadable to everyone but its author.
Note: A good concrete answer is that you would enforce a rule such as no owning raw pointers, backed by clang-tidy, rather than relying on people to remember. And the C++ Core Guidelines are worth naming — they are the closest thing the community has to agreed practice.
Technical Questions
1. What is RAII, and how do smart pointers implement it?
RAII — Resource Acquisition Is Initialisation — ties a resource's lifetime to an object's lifetime. The constructor acquires it, the destructor releases it. Because C++ guarantees destructors run when an object goes out of scope, including during stack unwinding from an exception, cleanup is automatic and cannot be forgotten.
It applies to any resource, not just memory: file handles, mutex locks, sockets, database connections.
Smart pointers are RAII for heap memory:
std::unique_ptr— exclusive ownership. Cannot be copied, only moved. Zero overhead compared to a raw pointer. This should be your default.std::shared_ptr— shared ownership with an atomic reference count; the object is destroyed when the last one goes away. It costs a control block and atomic increments, so use it only when ownership genuinely is shared.std::weak_ptr— observes ashared_ptrwithout owning it. Its purpose is breaking reference cycles, whichshared_ptralone cannot handle.
Note: Prefer make_unique and make_shared over new. They are exception-safe in argument evaluation, and make_shared allocates the object and control block in one go. The broader point is that in modern C++ you should essentially never write delete.
2. What is the difference between a pointer and a reference in C++?
Both give indirect access, but with different rules.
- A reference must be initialised when declared and can never be reseated to refer to something else. A pointer can be null and can be reassigned.
- A reference cannot be null — there is no valid way to create one from nothing — so a function taking a reference need not check.
- Syntax. A reference is used like the object itself; a pointer must be dereferenced with
*or->. - Pointer arithmetic exists; reference arithmetic does not.
- You can have a pointer to a pointer, but not a reference to a reference.
How to choose: use a reference when the parameter is mandatory and use const& to pass large objects without copying. Use a pointer when the argument is genuinely optional, when you need to reseat it, or when interfacing with a C API. std::optional is often clearer than a nullable pointer for an optional value.
Note: The important caveat is that a reference is not inherently safe — it can dangle. Returning a reference to a local variable, or holding a reference to a vector element after the vector reallocates, are both undefined behaviour despite the reference never being null.
3. Explain move semantics, rvalue references and std::move.
Before C++11, returning a large object or inserting into a container meant copying. Move semantics lets you transfer ownership of internal resources instead.
An lvalue has an identity you can take the address of; an rvalue is a temporary about to expire. An rvalue reference, written T&&, binds to those temporaries and says "this object is about to die, so you may steal from it".
class Buffer {
char* data_; size_t size_;
public:
Buffer(Buffer&& other) noexcept
: data_(other.data_), size_(other.size_) {
other.data_ = nullptr; // leave source valid and destructible
other.size_ = 0;
}
};A move constructor copies the pointer rather than the megabytes it points at — O(1) instead of O(n).
std::move does not move anything. It is a cast to an rvalue reference, telling the compiler it may select the move overload. After moving from an object, it is in a valid but unspecified state: you may destroy it or assign to it, but not rely on its contents.
Note: Mark move operations noexcept. std::vector will only use a move constructor during reallocation if it is noexcept — otherwise it copies to preserve the strong exception guarantee, and you silently lose the benefit.
4. What is the Rule of Three, Five and Zero?
These rules describe which special member functions must be defined together.
The Rule of Three (C++98): if you need to write any one of the destructor, copy constructor, or copy assignment operator, you almost certainly need all three. The reason is that needing a custom destructor implies the class manages a resource, and the compiler-generated copy operations will copy the pointer rather than the resource — producing a double free.
The Rule of Five (C++11) adds the move constructor and move assignment operator. Declaring a destructor or copy operation suppresses the implicit move operations, so a class following only the Rule of Three silently loses move optimisation.
The Rule of Zero is the modern guidance and the one to lead with: design classes so you need none of them. If every member is a type that manages itself — std::string, std::vector, std::unique_ptr — the compiler-generated versions are all correct, and there is no code to get wrong.
Note: The practical consequence is a separation of duties. Write resource-managing wrapper classes rarely and carefully, and let everything else follow the Rule of Zero. A class that both manages a raw resource and holds business logic is where these bugs live.
5. How does virtual inheritance and polymorphism work in C++, and why do base classes need virtual destructors?
Runtime polymorphism works through the virtual table. A class with virtual functions gets a hidden pointer to a table of function addresses; calling a virtual function through a base pointer looks up the derived implementation at runtime. The cost is one pointer per object, one indirection per call, and the loss of inlining.
Why the destructor must be virtual:
Base* p = new Derived();
delete p; // undefined behaviour if ~Base is not virtualWithout virtual, only ~Base runs. ~Derived never executes, so any resource the derived class owns leaks. The rule: any class intended to be inherited from and deleted polymorphically needs a virtual destructor. If a class is not meant to be a base, it does not need one — and adding it unnecessarily costs a vtable pointer.
Virtual inheritance solves the diamond problem. If B and C both inherit from A, and D inherits from both, D normally contains two copies of A — ambiguous and wasteful. Declaring class B : virtual public A makes them share one subobject.
Note: Use override on every overriding function. It makes the compiler verify that you are actually overriding something, catching the common bug where a signature differs slightly and you silently create a new function instead.
6. What is the STL, and what are the main container types and their complexity?
The Standard Template Library is built from containers, iterators, and algorithms, connected so that algorithms work on any container through iterators.
Sequence containers:
vector— contiguous, dynamically resized. Random access O(1), push_back amortised O(1), insert or erase in the middle O(n). This should be your default container — contiguity makes it cache-friendly, which usually beats better asymptotic complexity at real sizes.deque— O(1) insertion at both ends, random access O(1), but not contiguous.list— doubly linked. O(1) insert or erase given an iterator, but O(n) to find anything and poor cache behaviour. Rarely the right choice in practice.
Associative containers:
map/set— balanced binary trees. O(log n) lookup, and iteration in sorted order.unordered_map/unordered_set— hash tables. O(1) average lookup, O(n) worst case, no ordering.
Note: Iterator invalidation is the follow-up to be ready for. Inserting into a vector may reallocate and invalidate every iterator, pointer, and reference into it; list and map invalidate only what you erase. Modifying a container while iterating over it is one of the most common C++ bugs.
7. What is the difference between the stack and the heap in C++?
The stack holds local variables and function call frames. Allocation is a single register adjustment, so it is extremely fast; deallocation is automatic when scope ends. It is contiguous and cache-friendly. The limits are that its size is fixed at thread creation — typically one to eight megabytes — and that the size must be known at compile time.
The heap is memory obtained with new or malloc. It is large, sized at runtime, and outlives the scope that created it. The costs are a much slower allocator that must search for a free block, potential fragmentation, worse locality, and the obligation to free it.
How to choose: prefer the stack. Use the heap when the object is too large for the stack, when its size is only known at runtime, or when it must outlive the current scope. Even then, own it with unique_ptr or a container rather than a raw pointer.
The failure modes differ: exhausting the stack — usually via unbounded recursion or a huge local array — gives you a stack overflow and immediate crash. Exhausting the heap throws std::bad_alloc. Forgetting to free heap memory leaks; freeing twice or using after free is undefined behaviour and often exploitable.
Note: std::vector is a useful illustration — the vector object itself sits on the stack while its elements live on the heap, and its destructor frees them automatically.
8. What are templates in C++, and what is the difference between compile-time and runtime polymorphism?
A template is a blueprint the compiler uses to generate code for each type you instantiate it with. std::vector<int> and std::vector<string> are entirely separate generated classes.
template <typename T>
T max_of(const T& a, const T& b) {
return a > b ? a : b;
}Compile-time polymorphism (templates):
- Type resolution happens during compilation, so calls can be inlined and there is no runtime overhead.
- Costs: longer compile times, larger binaries from code generated per type, and historically dreadful error messages — which is what C++20 concepts were introduced to fix.
Runtime polymorphism (virtual functions):
- The type is resolved through the vtable while the program runs.
- Costs an indirection and prevents inlining, but lets you store heterogeneous types behind one base pointer and choose behaviour based on runtime data.
How to choose: if the set of types is known at compile time and performance matters, use templates. If you need a collection of different types handled uniformly, or the type depends on runtime input such as a configuration file, use virtual functions.
Note: Templates must generally be defined in headers, because the compiler needs the definition at each instantiation point. That is why STL headers are so large.
9. How do you handle concurrency in modern C++, and what causes a data race?
Since C++11 the standard library provides threading directly.
std::thread— a thread of execution. It must be joined or detached before destruction, or the program terminates.std::jthreadin C++20 joins automatically and supports cooperative cancellation.std::mutexwithstd::lock_guardorstd::unique_lock— RAII locking, so the mutex is released even if an exception propagates. Never calllock()andunlock()by hand.std::atomic— lock-free operations on single values, for counters and flags.std::condition_variable— for waiting until a condition holds. Always wait with a predicate, because spurious wakeups are permitted.std::asyncandstd::future— a higher-level way to get a result back from concurrent work.
A data race occurs when two threads access the same memory location concurrently, at least one writes, and there is no synchronisation between them. It is undefined behaviour, not merely a wrong value — the compiler is entitled to optimise on the assumption it cannot happen, so symptoms can be bizarre and unrelated.
How to avoid them: prefer immutable shared data or message passing over shared mutable state; protect what must be shared with a mutex; take locks in a consistent order to avoid deadlock; and run ThreadSanitizer in CI, because races are almost impossible to find by inspection.
10. What is const-correctness, and where can const be applied?
Const-correctness means marking everything that does not modify state as const, so the compiler enforces your intent. It documents the interface, prevents accidental modification, and enables optimisation.
Where it applies:
- Variables —
const int max = 100; - Function parameters —
void print(const std::string& s). Passing byconst&avoids a copy while promising not to modify. - Member functions —
int size() const;promises not to modify the object, and is required for the method to be callable on a const object. Omitting it is the most common const-correctness failure. - Return values — returning
const&to expose a member without allowing modification. - Pointers, in two distinct ways —
const char* pis a pointer to constant data (the data cannot change), whilechar* const pis a constant pointer (the pointer cannot be reseated).const char* const pis both. Read these right to left.
Note: Const-correctness is viral. If a low-level function is not const-correct, every caller is forced to give up const too, which is why it must be applied from the start — retrofitting it across a large codebase is genuinely painful. Also worth knowing: mutable exempts a member from const, which is legitimate for a cache or a mutex inside a const method.





