← All languages
C++ interview questions
40 curated C++ concept questions with model answers. Practice them out loud in the scored Fundamentals room.
C++ interviews test whether you can write efficient, safe code without hiding behind a garbage collector. Interviewers focus on RAII, move semantics, the difference between pointers and references, and undefined behaviour, because those determine whether a large codebase stays correct.
- RAII and smart pointers: how unique_ptr and shared_ptr express ownership, and why RAII makes exceptions safe.
- Move semantics and copy elision: what std::move actually does, and when a value is moved instead of copied.
- Pointers, references, and undefined behaviour: dangling pointers, aliasing, and why reading uninitialised memory is not "just a warning".
What interviewers are really testing
C++ interviewers test ownership and lifetimes. The questions are designed to surface whether you reason about who frees memory, when an object is moved rather than copied, and where undefined behaviour lurks — the details that decide whether a large codebase stays correct under change, exceptions, and concurrency.
Basic
What's the difference between a pointer and a reference?
- A reference is an alias that must be initialized and cannot be reseated; a pointer holds an address, can be null, and can be reassigned.
- References have no null state (assuming well-formed code) and are used for parameter passing; pointers express optionality and arithmetic.
- You dereference a pointer with * and take addresses with &; references are used directly.
- Const references (`const T&`) are the default for read-only parameters to avoid copies.
- Pointers support pointer arithmetic; references do not.
What is the difference between stack and heap memory?
- Stack: automatic storage tied to scope, fast, limited in size, freed when scope exits.
- Heap: dynamic storage via new/malloc, larger, manual or RAII-managed, slower to allocate.
- Stack objects are destroyed in reverse order of construction; heap objects live until deleted or a smart pointer releases them.
- Returning a pointer/reference to a stack object is undefined behavior (dangling).
- Modern C++ prefers stack objects and smart pointers over raw new/delete.
Explain const correctness in C++.
- `const T` prohibits modification; `const T&` passes by reference without copying or mutating.
- A const member function promises not to modify the object and can be called on const instances.
- `T* const` is a const pointer; `const T*` is a pointer to const — read right to left.
- const enables compiler optimization and documents intent.
- mutable allows a member to change even in a const method (used for caches/mutexes).
When do you pass by value vs by reference?
- By value copies (or moves) the argument; fine for cheap types like int, char, pointers.
- By reference avoids copies for large objects; use `const T&` for read-only.
- Non-const `T&` lets the function mutate the caller's object.
- Move semantics make pass-by-value reasonable for types designed to be moved (e.g. std::string) when you need a copy anyway.
- Returning by value is efficient with copy elision / RVO.
How do new/delete differ from malloc/free?
- new calls the constructor and returns a typed pointer; delete calls the destructor. malloc/free only allocate raw bytes.
- new throws std::bad_alloc on failure; malloc returns nullptr.
- Mismatched new/free or malloc/delete is undefined behavior; arrays need new[]/delete[].
- Modern C++ avoids explicit new/delete in favor of RAII and smart pointers.
- Placement new constructs into pre-allocated memory.
When would you use std::vector vs std::list?
- vector is a contiguous dynamic array: O(1) random access and append, O(n) insert/erase in the middle, great cache locality.
- list is a doubly linked list: O(1) splice/insert given an iterator, but O(n) access and poor locality.
- vector is the default container; list is rarely faster even for middle inserts due to cache effects.
- Use deque for fast push/pop at both ends, and unordered_map/map for keyed lookup.
- Capacity vs size: reserve() avoids reallocations when the final size is known.
What is function overloading and how is it resolved?
- Multiple functions with the same name but different parameter lists (types/count/order).
- The compiler resolves the call by overload resolution, ranking exact matches, promotions, and conversions.
- Return type alone cannot distinguish overloads.
- Ambiguous calls fail to compile.
- Name mangling encodes the parameter types so the linker can tell them apart.
What does virtual do and how does polymorphism work?
- A virtual function is dispatched at runtime based on the dynamic type of the object.
- Calling through a base pointer/reference invokes the derived override.
- Override the base destructor as virtual so deleting through a base pointer destroys the derived part.
- override/crucial keywords catch signature mistakes at compile time.
- Dynamic dispatch works through a vtable, which adds a small indirection cost.
What's the difference between struct and class in C++?
- The only language difference is default access: struct members and inheritance are public; class defaults to private.
- Convention: struct for plain data aggregates, class for types with invariants and behavior.
- Both support constructors, methods, inheritance, and access specifiers.
- Aggregate initialization works cleanly for structs.
- Being a 'POD' (plain old data) matters for ABI/serialization.
Why prefer std::string over C-style char arrays?
- std::string manages its own memory, knows its length, and handles growth — no manual buffer sizing or strlen.
- It copies/moves safely and integrates with the STL algorithms.
- C strings are null-terminated char arrays with manual allocation and buffer-overflow risk.
- std::string_view is a non-owning view for read-only parameters without copying.
- c_str() bridges to APIs that need a C string.
When do you use a C array instead of std::vector?
- A C array has a fixed size known at compile time and decays to a pointer.
- std::vector is a resizable, value-semantic container that knows its size and manages memory.
- Prefer vector or std::array (fixed size, with bounds/iterators) over raw arrays.
- Raw arrays don't carry their length, so they need a separate size parameter.
- std::array has no dynamic allocation and works with STL algorithms.
What are namespaces and the scope-resolution operator?
- Namespaces group names to avoid collisions; :: accesses a name in a namespace or the global scope.
- Nested namespaces can be declared as namespace a::b (C++17).
- using namespace in a header pollutes every includer; avoid it.
- using declarations (using std::cout) are narrower and safer.
- Anonymous namespaces give internal linkage for file-local helpers.
How do default arguments work and what are the rules?
- Trailing parameters can have defaults evaluated at the call site.
- Once a parameter has a default, all parameters after it must too.
- Defaults are part of the declaration, not the definition, and are fixed at compile time.
- They can't be overridden by a derived-class override; virtual dispatch uses the static type's default.
- Overloads and defaults can be ambiguous if not designed carefully.
How does operator overloading work and when is it a bad idea?
- You define operator functions (operator+, operator==) for a type, either as a member or a free function.
- It lets user types behave like built-ins, e.g. complex numbers or matrices.
- It must preserve intuitive meaning; overloading + to do something unrelated confuses readers.
- Some operators can't be overloaded (::, ., ?:), and &&/|| lose short-circuit if overloaded.
- Prefer named methods when no natural operator exists.
What are the meanings of static in C++?
- A static local variable is initialized once and persists for the program's life.
- A static member variable is shared by all instances and must be defined out of class (or inline).
- A static member function has no this and can be called without an instance.
- At namespace scope, static gives internal linkage (file-local).
- Function-local statics are initialized thread-safely in C++11.
How do exceptions work in C++?
- throw unwinds the stack to the nearest matching catch, running destructors of automatic objects (RAII).
- Catch by reference (const std::exception&) to avoid slicing and copies.
- Catch specific types before general ones; ... catches everything.
- An exception escaping a destructor during unwinding calls std::terminate.
- Mark non-throwing functions noexcept so the compiler can optimize and terminate on violation.
Why prefer enum class over a plain enum?
- enum class is scoped, so enumerators don't leak into the enclosing scope.
- It does not implicitly convert to int, which prevents accidental arithmetic.
- You can specify the underlying type (enum class Color : uint8_t).
- Plain enums do convert to int and can collide across enums.
- Both live in the same namespace; enum class is the modern default.
How does auto deduce types and when does it bite?
- auto deduces from the initializer, following template deduction rules.
- It drops top-level const and references, so auto x = expr can copy unknowingly — use const auto& to avoid that.
- auto&& is a forwarding reference that preserves value category.
- decltype(auto) preserves the exact type including references.
- Use auto when the type is obvious or unwieldy, but spell out types when it aids clarity.
What is brace (uniform) initialization?
- {} initializes anything: int x{0}, std::vector<int> v{1,2,3}, Point p{1,2}.
- Brace initialization forbids narrowing conversions, catching bugs at compile time.
- It avoids the most-vexing-parse (a declaration mistakenly read as a function).
- std::initializer_list overloads can take precedence and surprise you (e.g. vector<int> v{10}).
- Empty {} value-initializes, which is the modern default-construct idiom.
Why does an array lose its size when passed to a function?
- An array parameter decays to a pointer to its first element, so sizeof inside the function is the pointer size.
- Pass a pointer plus a size, or a std::array/std::span, to preserve length.
- std::span (C++20) is a non-owning view over a contiguous range with size.
- References to arrays (T(&)[N]) preserve the size but are verbose.
- This decay is a common source of out-of-bounds bugs.
Advanced
What is RAII and why is it central to C++?
- Resource Acquisition Is Initialization: tie a resource's lifetime to an object's scope — acquire in the constructor, release in the destructor.
- Destructors run deterministically when scope exits or during stack unwinding, so cleanup happens even on exceptions.
- It works for memory, files, locks (std::lock_guard), sockets, and DB handles.
- It removes most manual cleanup and try/finally-style code.
- Smart pointers and containers are RAII wrappers; that is why manual new/delete is discouraged.
Explain move semantics and rvalue references.
- An rvalue reference (T&&) binds to temporaries and enables stealing resources instead of deep-copying.
- A move constructor/assignment transfers ownership (e.g. pointer swap) and leaves the source in a valid-but-unspecified state.
- std::move casts an lvalue to an rvalue so a move can happen; it does not itself move anything.
- Moved-from objects must still be destructible and assignable.
- Move semantics make returning and storing large objects cheap.
Compare unique_ptr, shared_ptr, and weak_ptr.
- unique_ptr is exclusive ownership with zero overhead; it can be moved but not copied.
- shared_ptr is reference-counted shared ownership; the object is destroyed when the last owner goes away.
- weak_ptr observes a shared_ptr without owning; lock() upgrades it and detects expiration — it breaks reference cycles.
- shared_ptr overhead includes an atomic control block; constructing one with make_shared is more efficient.
- Prefer unique_ptr; reach for shared_ptr only when ownership is genuinely shared.
Why does a base class need a virtual destructor?
- Deleting a derived object through a base pointer with a non-virtual destructor is undefined behavior — only the base destructor runs, leaking derived resources.
- A virtual destructor dispatches to the most-derived destructor, then up the chain.
- Add it whenever a class is meant to be used polymorphically (has virtual functions).
- A pure virtual destructor can be declared and must still be defined.
- Rule: virtual functions imply a virtual destructor.
How do C++ templates work and what is specialization?
- Templates are compile-time blueprints instantiated for the types used — static polymorphism with no runtime cost.
- Function and class templates, variadic templates, and concepts (C++20) constrain them.
- Explicit/full specialization provides a distinct implementation for one type; partial specialization narrows a class template.
- Definition usually lives in headers because instantiation needs the source.
- Errors surface at instantiation, which can be noisy without concepts.
What is the Rule of Three/Five?
- If a class manages a resource and defines one of destructor, copy constructor, or copy assignment, it likely needs all three (Rule of Three).
- C++11 adds move constructor and move assignment, making it the Rule of Five.
- Declaring any of these suppresses the implicit move operations, so you must opt in deliberately.
- The Rule of Zero is better: store resources in RAII members (smart pointers, containers) and define none.
- Copy-and-swap is a common idiom for exception-safe assignment.
What is undefined behavior and why is it dangerous?
- UB is code the standard places no requirements on — the compiler may do anything, not just crash.
- Examples: out-of-bounds access, use-after-free, signed overflow, dereferencing null, data races, uninitialized reads.
- Optimizers assume UB never happens, so bugs can appear only at -O2 or with a different build.
- It is not the same as implementation-defined (which has a documented behavior) or unspecified (valid but not fixed).
- Tools: -fsanitize=address/undefined, valgrind, and static analyzers.
What is constexpr and how does it differ from const?
- constexpr means the value/function can be evaluated at compile time; const only means read-only.
- constexpr functions can also run at runtime; they are not restricted to compile time.
- It enables compile-time computation, array sizes, and template arguments.
- consteval forces compile-time evaluation; constinit ensures static initialization order safety.
- Prefer constexpr for constants so the compiler can fold them.
What is a vtable and what does dynamic dispatch cost?
- Each polymorphic class gets a vtable of function pointers; each object stores a hidden vptr to its class's vtable.
- A virtual call loads the vptr, indexes the vtable, and calls through a pointer — an indirect branch, typically hard to inline.
- Costs are one indirection plus prevention of inlining; usually acceptable, measurable in hot loops.
- Object size grows by the vptr (typically 8 bytes).
- final and devirtualization let the compiler turn calls back into static dispatch.
Explain lambda captures in C++.
- A lambda is an anonymous functor; the capture list decides which outer variables are stored and how.
- [=] captures by value (a copy at creation), [&] by reference (must outlive the lambda), and specific names can mix them.
- Capturing by reference a local that dies before the lambda runs is a dangling reference.
- A lambda that captures nothing converts to a function pointer.
- mutable allows modifying value-captured members inside the lambda body.
What is perfect forwarding?
- A forwarding reference (T&& in a deduced context) binds to both lvalues and rvalues, preserving value category.
- std::forward<T>(arg) passes it on with the original lvalue/rvalue-ness.
- It is essential for wrappers like make_unique, emplace_back, and factory functions.
- Without forwarding, an rvalue argument is treated as a named lvalue and copied.
- It pairs with reference collapsing (T& & -> T&, T& && -> T&).
What is the diamond problem and virtual inheritance?
- Diamond inheritance duplicates the common base subobject, causing ambiguity.
- virtual inheritance makes derived classes share a single base subobject.
- It adds indirection (vtable-like base pointers) and complexity.
- Prefer composition or interfaces (abstract bases with no data) over MI with state.
- Ambiguous member access is resolved with explicit qualification.
What are type traits and SFINAE?
- Type traits (<type_traits>) query and transform types at compile time (is_integral, enable_if, decay).
- SFINAE means a substitution failure in a template signature is not a hard error — it removes the overload.
- std::enable_if and void_t constrain overloads; concepts (C++20) replace most SFINAE with readable requires clauses.
- if constexpr branches at compile time inside templates.
- Traits power generic libraries and custom type requirements.
Explain alignment and struct padding.
- Every type has an alignment requirement; members are padded so each sits on its aligned offset.
- Total struct size is a multiple of the strictest member alignment.
- Reordering members can shrink a struct; order by descending size as a heuristic.
- alignas requests stricter alignment; alignof queries it.
- Padding bytes are not initialized and are not part of value comparison in C++20's operator==.
What are the C++ memory orders for atomics?
- std::atomic operations define ordering: relaxed, acquire, release, acq_rel, seq_cst.
- Relaxed gives atomicity but no ordering between threads.
- Release on store + acquire on load establishes a happens-before edge for data protected by the atomic.
- seq_cst is the default and the easiest to reason about, at some cost.
- Data races on non-atomic memory are undefined behavior regardless of atomics.
When do you need a custom deleter?
- When the resource isn't freed with delete: malloc/free, FILE*, sockets, library handles.
- unique_ptr<T, Deleter> stores the deleter in the type; shared_ptr stores it in the control block.
- A stateless deleter (lambda with no captures) adds no size to unique_ptr.
- Always use make_shared for the control block allocation; custom deleters force the two-allocation form.
- The deleter must be copy-constructible for shared_ptr.
What is the PIMPL idiom?
- Pointer to IMPLementation hides private members behind an opaque pointer to a forward-declared type.
- It reduces compile-time dependencies, since the header no longer includes the implementations' headers.
- It provides ABI stability: adding members doesn't change the class size.
- Cost: an extra allocation and indirection per object.
- The destructor must be defined in the .cpp where the impl type is complete.
What is CRTP?
- Curiously Recurring Template Pattern: class Derived : public Base<Derived>.
- It gives static (compile-time) polymorphism with no vtable, useful for mixins and operator generation.
- The base can call static_cast<Derived*>(this)->method() with no virtual dispatch.
- It cannot be used polymorphically through a Base pointer to a common type (each Base<D> is distinct).
- Used by enable_shared_from_this, iterator helpers, and expression templates.
What are the exception-safety guarantees?
- Basic guarantee: no leaks and invariants hold, but the object may be in a changed valid state.
- Strong guarantee: the operation either succeeds or leaves the state unchanged (commit-or-rollback).
- No-throw (nothrow) guarantee: the operation never throws (destructors, swap).
- Copy-and-swap is the classic way to get the strong guarantee.
- std::vector::push_back gives the strong guarantee only if the element's move/copy doesn't throw.
How do you write a move-only type?
- Delete the copy constructor and copy assignment; define move constructor and move assignment.
- Move-only types are stored and passed by value with std::move and returned by value.
- unique_ptr and std::thread are standard move-only types.
- Containers require move-only elements to be nothrow-movable for strong guarantees.
- A moved-from object must still be destructible and assignable.
Common mistakes
- Saying std::move moves anything — it only casts to an rvalue reference, and the move still has to be implemented.
- Using raw new and delete where a unique_ptr or a stack object would express ownership more clearly.
- Ignoring the rule of three/five, so a class with a destructor double-frees when it is copied.
- Assuming a dangling pointer or out-of-bounds read is harmless because "it usually works".
How to rehearse these answers
- Implement a class that owns a heap buffer with the rule of five and explain each special member function.
- Write a function taking a forwarding reference and show when the argument is moved versus copied.
- Show how RAII releases a file or lock during an exception and contrast it with manual try/catch cleanup.
- Explain the difference between shared_ptr and weak_ptr, including a reference cycle that leaks.
New to technical interviews? Read how to rehearse coding and system design.
Other languages
JavaScript interview questions · Python interview questions · Java interview questions · C# interview questions · Go interview questions