C++ Interview Questions

Commonly asked C++ interview questions on virtual destructors, RAII, smart pointers, and undefined behavior.

A curated set of C++ interview questions, ordered roughly from fundamentals to more advanced — the kind you'll actually be asked in real screens and on-sites.

Memory and ownership

Q: What is the difference between the stack and the heap? The stack holds local variables with automatic, extremely fast allocation/deallocation tied to scope — memory is reclaimed the instant a variable goes out of scope, with no bookkeeping needed. The heap holds memory explicitly requested at runtime (via new or a smart pointer) that persists until explicitly freed, which is slower to allocate but necessary when data must outlive the function that created it or when its size isn't known at compile time.

Q: Why must a base class destructor be virtual if the class is used polymorphically? If you delete a derived object through a base-class pointer and the base destructor isn't virtual, only the base class's destructor runs — the derived part of the object is never cleaned up. This is undefined behavior and a very common source of subtle resource leaks in hierarchies that weren't designed with polymorphic deletion in mind from the start.

Q: What's the difference between std::unique_ptr and std::shared_ptr? unique_ptr represents exclusive ownership of a heap object — it can't be copied, only moved, and has zero overhead compared to a raw pointer. shared_ptr represents shared ownership via atomic reference counting — copying it increments the count, and the object is destroyed only when the last owner goes away, at the cost of a control block and atomic operations on every copy/destroy. Default to unique_ptr; reach for shared_ptr only when ownership genuinely needs to be shared.

RAII and resource safety

Q: What is RAII, and what problem does it solve? RAII (Resource Acquisition Is Initialization) ties a resource's lifetime to an object's lifetime — you acquire the resource in the constructor and release it in the destructor. Because C++ guarantees destructors run on normal scope exit and during stack unwinding from an exception, RAII makes resource cleanup automatic and exception-safe, eliminating the need for manual try/finally-style cleanup and the leaks that come from forgetting it on some code path.

Q: Why should raw new/delete be rare in modern C++ code? Every manual new needs a matching delete on every possible exit path from the function — including early returns and exceptions — and missing even one path leaks memory. Standard containers (std::vector, std::string) and smart pointers (std::unique_ptr, std::shared_ptr) wrap this pattern in RAII, so cleanup happens automatically and correctly by construction, which is why idiomatic modern C++ code almost never calls delete directly.

Undefined behavior

Q: Give a concrete example of undefined behavior in C++ and explain why it's dangerous. Reading from or writing past the end of an array (int arr[5]; arr[10] = 1;) is undefined behavior — the compiler is allowed to assume it never happens, and no particular result is guaranteed: it might crash immediately, silently corrupt unrelated memory, or appear to "work" until a seemingly unrelated part of the program breaks later. This is what makes UB dangerous compared to, say, a Java ArrayIndexOutOfBoundsException — there's no guaranteed, consistent failure to catch in testing; tools like AddressSanitizer and -fsanitize=undefined exist specifically to surface these bugs during development.

Q: What is a dangling pointer, and how do smart pointers help avoid it? A dangling pointer points to memory that has already been freed (or to a stack variable that has gone out of scope) — dereferencing it is undefined behavior, and the bug is often silent until it manifests as corrupted data far away from the actual mistake. unique_ptr and shared_ptr avoid this by tying deletion to a clear, compiler-enforced ownership model: the pointed-to object is destroyed exactly when its owning smart pointer goes away, so there's no separate manual delete call that a programmer could mistime or forget.

Templates and the STL

Q: When you call a function template like maxOf<int>(3, 7) and later maxOf<double>(1.5, 2.5), is the compiler reusing one generic function at runtime? No — each distinct type used with a template causes the compiler to generate (instantiate) a separate, fully type-checked function specifically for that type, at compile time. By the time the program actually runs, there's no generic dispatch happening at all — only ordinary calls to concrete, already-generated functions — which is exactly why templates carry zero runtime overhead compared to hand-writing a separate function per type.

Q: Why does a class template's implementation typically have to live in the header file rather than a separate .cpp file? The compiler needs the template's complete definition visible at the point it's instantiated for a given type, and that instantiation can occur in any translation unit that uses the template, not only the one where it was originally written. Splitting the implementation into a separately compiled source file — the normal pattern for an ordinary class — would leave other files that only see the header with no definition to instantiate from, producing a linker error.

Q: What's the difference between std::map and std::unordered_map, and how do you choose between them? std::map keeps keys sorted, backed by a balanced tree, giving O(log n) operations and ordered iteration. std::unordered_map is backed by a hash table, giving O(1) average-case lookup and insertion but no defined iteration order. Default to std::unordered_map for raw lookup performance; reach for std::map specifically when sorted key iteration (or range queries over keys) is actually needed.

Concurrency

Q: What is a data race, and why is it undefined behavior rather than just "an unpredictable number"? A data race happens when two threads access the same memory location concurrently with no synchronization between them, with at least one of them writing — the C++ standard doesn't just say the result is unpredictable, it says the program's behavior is entirely undefined, no different in principle from other categories of UB like an out-of-bounds array access. In practice this often does surface as a wrong or inconsistent number (e.g., a shared counter incremented from multiple threads without a mutex), but the compiler and hardware are formally free to do anything at all once a race exists, including behavior that looks fine in testing and fails only later under different timing.

Q: Why is std::lock_guard generally preferred over calling a mutex's lock() and unlock() directly? std::lock_guard acquires its mutex on construction and releases it automatically in its destructor the moment it goes out of scope — including when an exception unwinds the stack partway through the protected section — the same RAII idea used elsewhere in C++ for resource cleanup. Manual lock()/unlock() calls require correctly unlocking on every exit path by hand, and a single missed unlock() (easy to overlook on an early return or an exception) leaves the mutex permanently held, deadlocking any other thread that subsequently tries to acquire it.