Top C++ Interview Questions & Answers

C++ interviews test your understanding of manual memory management, ownership, and performance tradeoffs that most managed languages abstract away. These are the questions that come up most for systems, gaming, and performance-critical roles.

Take Practice Interview Now
Improve your chances of getting selected — rehearse these answers out loud with an AI interviewer and get a scored report in minutes.
Start Free →

1. What's the difference between a pointer and a reference in C++?

A pointer is a variable that stores a memory address and can be reassigned to point at different objects, or set to `nullptr` to indicate it points at nothing. A reference is an alias for an existing object, established at initialization, and it can never be null or rebound to refer to something else afterward. Because references must be initialized when declared, they're generally safer and preferred for function parameters where you don't need to represent absence or reseating. Pointers support pointer arithmetic and can be stored in containers, sized, or compared, none of which references can do meaningfully. Under the hood a reference is usually implemented as a pointer by the compiler, but the language enforces stricter usage rules on it. In interviews, a good example is: use a reference when a value is required and won't change identity, use a pointer when you need optional-ness, reseating, or arrays.

2. What does const correctness mean, and why does it matter?

Const correctness means using the `const` qualifier wherever a value, pointer, or method shouldn't modify the object it touches, and letting the compiler enforce that contract at compile time. A `const` variable can't be reassigned after initialization, a `const` pointer like `int* const p` can't be repointed, while `const int* p` means the pointee can't be modified through that pointer. Marking a member function `const` promises callers it won't mutate the object's observable state, which lets you call that method on `const` references and objects. It also documents intent, catches accidental mutation bugs early, and improves optimizer opportunities since the compiler can rely on the guarantee. A common gotcha is `mutable` members, which let you bypass const on specific fields like caches or mutexes inside an otherwise const method. Overall it's one of the cheapest ways to make an API self-documenting and harder to misuse.

3. Explain manual memory management with new/delete and how RAII helps.

`new` allocates memory on the heap and calls the constructor, while `delete` calls the destructor and releases that memory back to the heap; the array versions `new[]` and `delete[]` must be paired correctly or you get undefined behavior. The core danger with manual management is that every `new` needs a matching `delete` on every code path, including early returns and thrown exceptions, which is easy to get wrong and leads to memory leaks or double frees. RAII, Resource Acquisition Is Initialization, solves this by tying a resource's lifetime to an object's scope: you acquire the resource in the constructor and release it in the destructor, so when the object goes out of scope the destructor runs automatically, even during stack unwinding from an exception. `std::vector`, `std::string`, and smart pointers like `unique_ptr` are all RAII wrappers around raw resources. In modern C++ you should almost never call `new` and `delete` directly in application code; you wrap the allocation in an RAII type once and let the language's scoping rules handle cleanup everywhere else. This is why resource management via destructors is considered one of C++'s defining idioms.

4. What's the difference between stack and heap allocation?

Stack allocation happens automatically for local variables and function parameters; the compiler reserves space on the call stack when a scope is entered and pops it when the scope exits, so allocation and deallocation are essentially free and deterministic. Heap allocation, done via `new` or `malloc`, comes from a separate memory pool managed at runtime, persists until you explicitly free it, and is noticeably slower because it involves bookkeeping in the allocator and potential fragmentation. Stack memory is limited in size, typically a few megabytes, so large arrays or deeply recursive calls can cause a stack overflow, whereas heap memory is limited mainly by available system memory. Objects on the stack have lifetimes tied to their scope, while heap objects live until you delete them or a smart pointer's reference count drops to zero. Stack allocation is also cache-friendlier since it's contiguous and predictable, which matters for performance-sensitive code. As a rule of thumb, prefer the stack for anything with a well-defined, short lifetime and reach for the heap when you need data to outlive the current scope or when the size isn't known until runtime.

5. What's the difference between unique_ptr and shared_ptr?

`unique_ptr` represents exclusive ownership of a heap object; it can't be copied, only moved, and when it goes out of scope it deletes the object it owns, with essentially zero overhead compared to a raw pointer. `shared_ptr` represents shared ownership through reference counting: every copy increments an atomic reference count, and the underlying object is deleted only when the last `shared_ptr` pointing to it is destroyed. Because of that atomic counter and the separate control block it allocates, `shared_ptr` carries real runtime overhead, so you shouldn't default to it when `unique_ptr` would express the ownership just as well. You typically choose `unique_ptr` for single-owner scenarios like a factory function returning a heap-allocated object, and `shared_ptr` when multiple parts of the program genuinely need to co-own an object's lifetime, like a cache or a graph node. You can transfer a `unique_ptr` into a `shared_ptr` but not the other way around. Both eliminate the need for manual `delete` calls and make ownership explicit in the type itself, which is a big readability win over raw pointers.

6. What is weak_ptr for, and why can't shared_ptr alone solve the problem it solves?

`weak_ptr` holds a non-owning reference to an object managed by a `shared_ptr`, meaning it doesn't affect the reference count and doesn't keep the object alive. The classic problem it solves is a reference cycle: if two objects hold `shared_ptr`s to each other, neither reference count ever reaches zero, so neither object is ever destroyed, causing a memory leak. Breaking that cycle by making one side a `weak_ptr` lets the object still be destroyed normally once the owning `shared_ptr`s go away, because the weak reference doesn't count. To actually use the object a `weak_ptr` points to, you call `lock()`, which returns a `shared_ptr` if the object is still alive or an empty one if it's already been destroyed, giving you a safe way to check for dangling access. It's commonly used for things like a child node holding a `weak_ptr` back to its parent, or an observer pattern where the observer shouldn't keep the subject alive. In short, `shared_ptr` answers "who owns this," and `weak_ptr` answers "I want to look at this without owning it."

7. How do virtual functions enable polymorphism in C++?

A `virtual` function tells the compiler to resolve the call at runtime based on the actual type of the object rather than the static type of the pointer or reference used to call it, which is called dynamic dispatch. The mechanism is typically implemented with a vtable, a per-class table of function pointers, and each object with virtual functions carries a hidden vptr pointing to its class's vtable; the call goes through that pointer to find the right override. This is what lets you write code against a base class pointer or reference, like `Shape*`, and have `draw()` correctly invoke `Circle::draw()` or `Square::draw()` depending on what the pointer actually points to. Without `virtual`, calling a function through a base pointer always calls the base class version, called static or early binding, and can silently produce wrong behavior. Virtual dispatch does carry a small runtime cost, an indirect call through the vtable, and it prevents inlining, but that's usually negligible compared to the design flexibility it buys. `override` is a C++11 keyword you should use on derived overrides so the compiler catches signature mismatches at compile time instead of silently creating a new, unrelated function.

8. Why do you need a virtual destructor in a base class?

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, not the derived class's, which means any resources the derived class acquired never get released. Marking the base destructor `virtual` makes destruction go through the vtable just like any other virtual call, ensuring the derived destructor runs first and then chains up to the base destructor, so the full object is torn down correctly. This matters specifically for polymorphic base classes, ones meant to be used through pointers or references to the base type; if a class is never going to be used polymorphically, you don't strictly need a virtual destructor. A good rule of thumb taught in most style guides is that any class with at least one other virtual function should also have a virtual destructor. Forgetting this is a classic source of subtle memory and resource leaks that only show up when you delete through the base pointer, which is exactly the scenario polymorphic code is built around. `std::unique_ptr<Base>` and `std::shared_ptr<Base>` have the same requirement, since they call `delete` on the stored pointer internally.

9. What are the rule of three, rule of five, and rule of zero?

The rule of three says that if a class needs a custom destructor, copy constructor, or copy assignment operator, it almost certainly needs all three, because managing a resource like a raw pointer or file handle usually requires defining all of them consistently. The rule of five extends this for C++11: once move semantics enter the picture, if you define any of destructor, copy constructor, copy assignment, move constructor, or move assignment, you should generally define all five, since the compiler stops generating some of them implicitly once you customize others. The rule of zero is the modern preferred approach: design your classes so they don't manage raw resources directly at all, instead composing them from RAII members like `std::vector`, `std::string`, or smart pointers, so the compiler-generated special member functions are correct by default and you don't write any of the five yourself. Following rule of zero drastically reduces bugs because hand-written copy or move logic is a common source of double frees, slicing issues, and shallow-copy bugs. In practice, experienced C++ engineers push resource-owning logic into a small number of low-level RAII wrapper classes and let everything above them follow rule of zero. This is one of the most telling things an interviewer looks for, because it shows you think about ownership design rather than just knowing the syntax.

10. What are move semantics and rvalue references, and why were they introduced in C++11?

An rvalue reference, written with `&&`, binds to temporary objects, values that are about to be destroyed anyway, like the return value of a function or the result of `std::move`. Move semantics let you define a move constructor and move assignment operator that steal the internal resources, such as a heap buffer, from that temporary instead of doing a deep copy, leaving the source object in a valid but unspecified, typically empty, state. This matters for performance: before C++11, returning a large object like a `std::vector` by value, or pushing it into a container, often meant an expensive copy, whereas now the compiler can move the resource instead, which is just a pointer swap. `std::move` doesn't actually move anything itself, it's just a cast that turns an lvalue into an rvalue reference so overload resolution picks the move overload instead of the copy overload. This is also the foundation of features like perfect forwarding with `std::forward` and emplace-style container APIs that construct objects in place instead of copying them in. In interviews, a good concrete example is that moving a `std::vector<int>` with a million elements is O(1), just copying a few pointers, while copying it is O(n).

11. How do templates work in C++ and why use them?

Templates let you write generic code parameterized on types or values, and the compiler generates a concrete version of the function or class for each distinct set of arguments it's actually used with, a process called template instantiation. This happens at compile time, so a function template like `template<typename T> T max(T a, T b)` produces zero runtime overhead compared to hand-writing separate `max` functions for `int`, `double`, and so on, unlike dynamic polymorphism with virtual functions. Class templates work the same way; `std::vector<T>` is really a template that gets instantiated into a distinct type for every `T` you use, so `vector<int>` and `vector<std::string>` are unrelated types under the hood. Because instantiation happens per translation unit based on usage, template code usually lives in header files, and errors in template code can produce notoriously long and hard-to-read compiler error messages. Templates also support specialization, letting you provide a different implementation for a specific type, and variadic templates, which let a template accept an arbitrary number of arguments, as used in `std::make_unique` or `std::tuple`. The tradeoff versus virtual functions is compile time and binary size, since heavily templated code can bloat the executable through code duplication across instantiations, but you get type safety and no runtime dispatch cost.

12. How do vector, list, and map compare in terms of performance characteristics?

`std::vector` stores elements contiguously in memory, giving O(1) random access via indexing and excellent cache locality, but inserting or erasing in the middle is O(n) because it has to shift elements, and growing beyond capacity triggers a reallocation that copies or moves everything. `std::list` is a doubly linked list, so insertion and erasure anywhere, given an iterator, is O(1) and iterators stay valid after insertions elsewhere in the list, but there's no random access, indexing is O(n), and the extra pointers per node plus scattered memory make it much less cache-friendly than a vector. `std::map` is typically implemented as a balanced binary search tree, usually a red-black tree, giving O(log n) insertion, lookup, and deletion, and it keeps elements sorted by key automatically, which `std::unordered_map` doesn't. If you need average O(1) lookup by key and don't care about ordering, `std::unordered_map`, a hash table, usually beats `std::map` in raw speed. In practice, `vector` should be your default container because of cache behavior, and you reach for `list` only when you're doing frequent mid-sequence insertions with stable iterators, and `map` or `unordered_map` when you need key-based lookup. Interviewers often want to hear you say "prefer vector by default" explicitly, since it signals you understand real-world performance over theoretical big-O alone.

13. What is operator overloading and what are some rules for using it well?

Operator overloading lets you define what an operator like `+`, `==`, or `<<` means for your own types, by defining a function named `operator+` or similar, either as a member function or a free function. It makes user-defined types behave more like built-in ones, for example letting you write `a + b` for two `Vector3` objects instead of `a.add(b)`, which improves readability when the semantics genuinely match the operator's usual meaning. A common convention is that operators requiring implicit conversion of the left-hand operand, or that need symmetry like `operator<<` for streams, are defined as free functions, often friends, while operators that naturally modify the object, like `operator+=` or `operator[]`, are defined as members. You should overload operators only when the meaning is intuitive and matches programmer expectations; overloading `+` to do something unrelated to addition is considered bad practice and confuses readers. It's also good practice to define related operators consistently, like implementing `operator==` and then `operator!=` in terms of it, or defining `operator<` for compatibility with `std::sort`. Since C++20, defining `operator<=>`, the spaceship operator, can automatically generate the other comparison operators for you, reducing this boilerplate.

14. What does the inline keyword actually do?

`inline` is a hint to the compiler that it may substitute the function's body directly at the call site instead of generating a call, eliminating call overhead for small, frequently called functions, but modern compilers largely ignore the hint and decide on their own based on optimization heuristics. The more important and guaranteed effect of `inline` today is about linkage: it tells the linker that a function's definition may appear identically in multiple translation units without causing a multiple-definition error, which is why functions defined inside a class body, or utility functions defined in headers, are implicitly or explicitly marked `inline`. Without that, defining a non-inline function in a header and including that header in multiple `.cpp` files would cause linker errors from duplicate symbol definitions. So in practice, when someone writes `inline` in modern C++, they're usually solving the one-definition-rule problem for header-only code rather than actually forcing the compiler to inline anything. `constexpr` functions are implicitly `inline` for the same reason. It's worth knowing this distinction in an interview because a lot of people still think `inline` is purely a performance keyword, when its practical use today is almost entirely about header-file linkage.

15. What are the different uses of the static keyword in C++?

Inside a function, a `static` local variable is initialized only once and retains its value between calls, giving you persistent state without exposing it as a global, and its initialization is thread-safe as of C++11. At namespace or file scope, `static` gives a variable or function internal linkage, meaning it's only visible within that translation unit, which is the traditional way to avoid symbol collisions before namespaces and anonymous namespaces became common. Inside a class, a `static` data member is shared by all instances of the class rather than each object having its own copy, and it needs to be defined once outside the class, unless it's `constexpr` or `inline static`; a `static` member function belongs to the class rather than any instance, so it has no `this` pointer and can only access other static members directly. A common example is a counter that tracks how many objects of a class currently exist, incremented in the constructor and decremented in the destructor, stored as a `static` member. Because static members and static locals have lifetimes spanning the whole program, they're subject to static initialization order issues across translation units, which is a subtle bug source. Interviewers often ask you to distinguish the "shared across instances" meaning inside a class from the "persists across calls" meaning inside a function, since it's the same keyword doing genuinely different jobs.

16. How does exception handling work in C++?

C++ exception handling uses `try` blocks to wrap code that might fail, `throw` to signal an error by throwing an object, usually derived from `std::exception`, and `catch` blocks to handle specific exception types, matched in order from top to bottom. When an exception is thrown, the runtime searches up the call stack for a matching handler, and as it unwinds each stack frame along the way, it calls the destructors of all local objects in those frames, which is exactly why RAII is so important in exception-safe code: resources get cleaned up automatically even when you don't explicitly catch the error at that level. If no handler matches anywhere up the stack, `std::terminate` is called and the program aborts. You should generally catch exceptions by reference, `catch (const std::exception& e)`, rather than by value, to avoid slicing derived exception types down to the base type. Functions can be marked `noexcept` to promise they won't throw, which enables certain optimizations and is required in some contexts like move constructors used by `std::vector` during reallocation. Exceptions are best used for truly exceptional, rare error conditions rather than routine control flow, since the machinery has real cost when actually thrown, though it's typically zero-cost when not thrown on most modern implementations.

17. Can you give some examples of undefined behavior in C++?

Dereferencing a null or dangling pointer is undefined behavior; the classic case is returning the address of a local variable from a function and then using it after the function returns, since that stack memory has already been reclaimed. Signed integer overflow, like incrementing `INT_MAX`, is undefined behavior in C++, unlike unsigned overflow which is well-defined to wrap around, which surprises a lot of people coming from other languages. Accessing an array out of bounds, for example `arr[10]` on a 10-element array which only has valid indices 0 through 9, doesn't reliably crash, it just reads or writes whatever memory happens to be there, which is what makes buffer overflows dangerous rather than just wrong. Using an object after it's been moved from, beyond just checking it's in a "valid but unspecified state," or using a variable that was never initialized, are also undefined behavior. Data races, where two threads access the same non-atomic memory location without synchronization and at least one is a write, are undefined behavior in the C++ memory model, not just a logic bug. The dangerous part of all of these is that undefined behavior doesn't guarantee a crash or an error message; the program might appear to work correctly for years and then fail unpredictably when the compiler, optimization level, or platform changes, which is why tools like `-fsanitize=address` and `-fsanitize=undefined` are so important in real projects.

18. What are the stages of compiling a C++ program?

The first stage is preprocessing, where the preprocessor handles everything starting with `#`, expanding `#include` directives by textually inserting header contents, substituting `#define` macros, and resolving `#ifdef` conditional blocks, producing a single expanded translation unit. Next is compilation proper, where the compiler takes that preprocessed source, checks syntax and types, and translates it into assembly code; this is also where templates get instantiated and optimizations are applied. The assembler then turns that assembly into machine code, an object file, typically a `.o` or `.obj` file, containing binary instructions along with a symbol table listing what names it defines and what it still needs from elsewhere. Finally, the linker takes all the object files for a program, plus any static or dynamic libraries, and resolves those symbol references, matching each function or variable call to its actual definition, and produces the final executable; this is the stage where you get "undefined reference" errors if a symbol was declared but never defined anywhere. Each translation unit, roughly one `.cpp` file plus everything it includes, goes through preprocessing and compilation independently, which is why the same header can be processed multiple times across different `.cpp` files and why include guards or `#pragma once` matter. Understanding these stages helps you reason about real errors: a missing header causes a compile error, a missing function definition causes a link error, and a macro problem often only becomes visible if you inspect the preprocessed output directly.

Take Practice Interview Now
Improve your chances of getting selected — rehearse these answers out loud with an AI interviewer and get a scored report in minutes.
Start Free →
Related topics
JavaPythonJavaScriptTypeScript