C++ Interview Questions and Answers: 30 Theory Questions
Thirty C++ interview questions answered: RAII, the rule of five, std::move, smart pointers, virtual dispatch, slicing, STL containers, noexcept and data races.
- Course: C++ study plan
- Module: Interview idioms
- Kind: Lesson
- Reading time: 25 min
- Runtime: Clang 18 · C++20
What are the most common C++ interview questions?
The most common C++ theory questions cover RAII, the rule of three, five and zero, what std::move does, unique_ptr versus shared_ptr, how virtual dispatch works through a vtable, why base-class destructors must be virtual, object slicing, std::map versus std::unordered_map, iterator invalidation, noexcept and data races. A strong answer gives the definition, names the mechanism and states the one consequence that matters.
Lesson
Every C++ interview has a theory section, and its questions have barely changed since C++11: the rule of five, how a virtual call works, what std::move actually does, when shared_ptr is right. This lesson is the drill: thirty questions, each with the answer an interviewer wants to hear — the definition first, the one consequence that matters, then a stop so the follow-up can come — and a pointer to the module that teaches it in depth. Read it twice a week before interviews. A C++ answer is expected to name a mechanism: not "moves are faster" but "a move steals the pointer and leaves the source empty".
The language and the toolchain
- What happens between a
.cppfile and a running program? The preprocessor expands#includeand macros into a translation unit; the compiler turns each unit into an object file, checking every use against a declaration; the linker joins the object files and libraries, resolving each name to exactly one definition. A compile error lives inside one unit; an "undefined reference" is a linker error — declared, never defined. (Module 1)
- What is undefined behaviour, and how does it differ from implementation-defined and unspecified? A construct the standard places no requirements on — signed overflow, an out-of-range index, a dangling dereference, a data race — so the compiler may assume it never happens and optimise on that assumption, which is why
if (x + 1 < x)can be deleted. Implementation-defined behaviour is documented by the compiler (sizeof(int)); unspecified behaviour is one of several allowed outcomes (the order function arguments are evaluated). (1, 19)
constversusconstexpr?constpromises the value will not change after initialisation, which may happen at run time —const int n = read();is legal.constexprpromises the value is computable at compile time, so it may be used where a constant expression is required: an array bound, a template argument, astatic_assert. Aconstexprfunction still runs at run time on run-time arguments. (2, 16)
- What does
autodeduce? The type of the initialiser with references and top-levelconststripped, by the rules of template argument deduction:auto x = ref;is a copy,auto& xkeeps the reference,const auto&binds to anything without copying. The traps areauto total = 0;— anint— andfor (auto s : names), which copies every string. (2)
- References versus pointers? A reference is an alias that must be bound at initialisation, cannot be reseated and cannot be null; a pointer is a value that can be null, reassigned and used in arithmetic. A reference for a parameter that must refer to an object, a pointer for "may be absent" or "may change what it points to", a smart pointer for ownership. Both can dangle. (6)
- By value, by reference or by
constreference? By value for anything register-sized or that the function needs its own copy of anyway; byconst T&for anything larger that is only read; byT&for an in-out parameter, which should be rare because returning a value is clearer and elided.std::string_viewandstd::spanare the modern spellings of "read-only, any source". (4)
- What is
std::string_view, and what is its hazard? A non-owning pointer-plus-length over characters it does not own: cheap to copy, O(1)substr, the right parameter type for read-only text. It dangles the moment the string it views dies —std::string_view sv = s + "x";views a temporary destroyed at the semicolon — so it is a parameter or a local, never a member that outlives its source. (5)
- What does a lambda capture, and what can go wrong?
[=]copies each variable it uses into the closure;[&]stores references. A[&]lambda that outlives its scope — returned, stored, handed to a thread — refers to dead locals; capture by value, or[x = std::move(x)], when the lambda escapes. Capturingthisis a pointer by another name. (4, 14)
Objects and ownership
- What is RAII? Resource Acquisition Is Initialisation: a resource's lifetime is tied to an object's lifetime — acquired in the constructor, released in the destructor, which runs on every exit from the scope, including exceptions and early returns. It is why C++ needs no
finally:std::vector,std::unique_ptr,std::lock_guardandstd::ofstreamare all RAII, and a raw owning pointer with no destructor is the bug. (7)
- The rule of three, five and zero? If you write any of the destructor, copy constructor or copy assignment, you almost certainly need all three, because the memberwise defaults copy a pointer and free it twice. Five adds the move constructor and move assignment: a user-declared destructor or copy suppresses the implicit moves, so an owning class without them copies where it could move. Zero is the alternative — let
std::vector,std::stringandstd::unique_ptrmembers own the resources and write none. (9)
- What does
std::movedo? Nothing at run time. It is a cast to an rvalue reference that lets overload resolution pick the move constructor or move assignment; the move happens there, and the source is left valid but unspecified — fit for assignment and destruction, not for anything that assumes its contents.std::moveon aconstobject silently selects the copy. (9)
- Why return by value? Because the copy is elided: since C++17 returning a prvalue constructs the result directly in the caller, and a named local is elided by NRVO or, failing that, moved.
return std::move(local);switches the elision off and is a pessimisation. Passing an output reference costs clarity and gains nothing. (9)
std::unique_ptrversusstd::shared_ptr? Exclusive ownership, one pointer wide, move-only, freed in the destructor: the default. Shared ownership through a reference-counted control block, copyable, an atomic increment per copy, freed when the last owner goes: for lifetimes that are genuinely shared, which is rarer than it looks.std::make_sharedputs object and control block in one allocation. (7)
- Why can
shared_ptrleak, and what isweak_ptr? Two objects holdingshared_ptrs to each other never reach a count of zero, so neither is freed. Astd::weak_ptrobserves without owning: it does not count, andlock()yields ashared_ptrif the object is still alive or an empty one if not. The back edge of a parent–child relationship is its textbook place. (7)
- Why does modern C++ almost never write
new? Because everynewis adeletesomeone must write on every path, including the exceptional ones; forgetting one is a leak and writing two is a double free.std::make_uniqueand the containers put thedeletein a destructor once, and a raw pointer then means "borrowed, not owned". (7)
- Name three ways to dangle. Return a reference to a local; keep a reference or pointer into a
std::vectoracross apush_backthat reallocates; take astd::string_vieworc_str()from a temporarystd::string. A fourth is a[&]lambda that escapes its scope. All are undefined behaviour, and all may "work" until the memory is reused. (6, 7, 13)
Polymorphism and templates
- How does virtual dispatch work? A class with virtual functions carries a hidden pointer to a table of function pointers, one table per class; a call through a base pointer or reference loads the vptr, indexes the table and calls indirectly — one extra load, an indirect branch, and a pointer added to the object. The call is resolved by the dynamic type, which is why a
Dogbehind anAnimal&barks. (10)
- Why must a base class destructor be virtual?
delete basePtr;on a base without a virtual destructor runs only the base destructor: the derived part is never destroyed, and the behaviour is undefined. Any class meant to be deleted through a base pointer declaresvirtual ~Base() = default;— astd::vector<std::unique_ptr<Shape>>needs it too. (10)
- What is object slicing? Copying a derived object into a base value —
Base b = derived;, a by-value parameter, astd::vector<Base>— keeps only the base part; the derived members and the dynamic type are gone, and virtual calls on the copy run the base versions. Pass and store by reference, pointer orstd::unique_ptr<Base>. (10)
- Overloading, overriding and hiding? Overloading is several functions with one name and different parameters, chosen at compile time by the static types of the arguments. Overriding is a derived class replacing a base virtual function with the same signature, chosen at run time by the dynamic type;
overridemakes the compiler check it. Hiding is a derived class declaring a non-virtual function with the same name: chosen by the static type, soAnimal* acalls the base version whatever the object is. (4, 10)
- Templates versus inheritance for polymorphism? Templates give static polymorphism: resolved at compile time, inlinable, no vtable, one instantiation per type, errors at instantiation —
std::sortwith a comparator. Virtual functions give dynamic polymorphism: one compiled function, mixed types in one container through a base pointer, an indirect call per invocation —std::vector<std::unique_ptr<Shape>>. Concepts give the template style a stated interface and readable errors. (12)
- The four casts?
static_castfor conversions the language already knows (numeric, up the hierarchy,void*back to its type);dynamic_castfor a checked downcast on a polymorphic base, returningnullptron a pointer and throwingstd::bad_caston a reference;const_castto removeconst, a smell;reinterpret_castto reinterpret bits, for serialisation and little else. A C-style cast tries all of them, which is why it is avoided. (2, 10)
The standard library
std::mapversusstd::unordered_map? A balanced tree: O(log n) everything, iteration in key order,lower_bound, needs only<. A hash table: O(1) average and O(n) worst, unspecified iteration order, needs a hash and==. The tree when order or range queries matter, the hash for pure lookup — and in both,operator[]inserts a default on a miss. (13)
- Why is
push_backamortised O(1), and what does it invalidate? When size reaches capacity the vector allocates a larger block — roughly double — and moves the elements across, so over n pushes the moves total less than 2n. That reallocation invalidates every iterator, pointer and reference into the old block, which is whyint& first = v[0]; v.push_back(x);dangles and whyreserveup front is faster and safer. (13)
- How do you erase while iterating?
erasereturns the iterator after the removed element, so the loop isfor (auto it = c.begin(); it != c.end(); ) { if (…) it = c.erase(it); else ++it; }— never++iton the iterator you erased. For a vector,std::erase_if(v, pred)or the erase–remove idiom does it in one pass. (13)
std::vectorversusstd::list? Contiguous storage: O(1) index, cache-friendly traversal, amortised O(1) append, O(n) insert in the middle, invalidation on reallocation. A doubly linked list: O(1) insert and erase at a known position and iterators that stay valid, but every element is an allocation and a cache miss, sovectorwins almost everywhere.listearns its place forsplice— the O(1) node move an LRU cache needs. (13, 19)
- What is a strict weak ordering, and why does
std::sortneed it? A comparator that is irreflexive (comp(a, a)is false), asymmetric, transitive, and whose "neither is less" relation is also transitive.std::sort,std::setandstd::mapassume it;<=in the comparator, ora.x < b.x || a.y < b.y, breaks it, and the result is undefined behaviour — a crash or an infinite loop, not merely a wrong order.std::tieis correct by construction. (14)
Errors and concurrency
- Exceptions versus error codes? Exceptions separate the error path from the happy path, cannot be ignored, and unwind the stack running destructors — the tool for a failure the immediate caller cannot handle. Return values (
bool,std::optional,std::error_code, C++23'sstd::expected) keep the failure local and visible in the signature — the tool for an expected outcome such as "key not found". Throw at the boundary, return values inside, never use exceptions for control flow. (15)
- What does
noexceptmean, and why doesstd::vectorcare? A promise that the function does not throw; if it does,std::terminateruns. Move constructors and destructors should benoexcept:std::vectormoves its elements during reallocation only when the move cannot throw and copies otherwise, because a throwing move half-way through would leave both buffers partly moved. The three guarantees — no-throw, strong (all or nothing), basic (valid but unspecified) — are what you promise per function. (15)
- What is a data race, and how do you prevent it? Two threads accessing the same memory location without synchronisation, at least one writing: undefined behaviour, not "sometimes the wrong number". Prevent it with a
std::mutexheld through astd::lock_guardorstd::scoped_lockaround every access, orstd::atomicfor a single counter or flag. Deadlock is two threads each holding a lock the other wants; avoid it with one lock, a fixed lock order, orstd::scoped_locktaking both at once. (17)
The follow-ups they actually ask
- After 2: "So is
intbeing 32 bits undefined?" — no, implementation-defined; and1 << 31on it wraps since C++20 but was undefined before. - After 10: "Show me the rule-of-five class with a
std::vectormember." — there is none: that is the rule of zero. - After 11: "What does
ahold afterauto b = std::move(a);?" — for astd::stringon libstdc++, nothing; the standard says only valid-but-unspecified, so never depend on it. - After 17: "Is a virtual call inside a constructor virtual?" — it dispatches to the class being constructed, because the derived part does not exist yet.
- After 20: "What is printed?" —
describe(const Animal&)anddescribe(const Dog&)called with anAnimal&to aDog: theAnimaloverload (static type), whilea.speak()is theDogoverride (dynamic type). - After 23: "What if every key hashes to the same bucket?" — one chain, O(n) per operation; rehashing fixes a high load factor, not a bad hash.
- After 30: "Is
std::atomic<int> n; n++enough for a counter?" — yes;n = n + 1is not.
How to answer theory questions
Lead with the one-sentence definition, add the consequence that matters, stop. Name the mechanism, not the adjective: "an indirect call through the vtable" beats "it is slower"; "the pointer is copied and the source nulled" beats "it is moved". Cite the standard version when you know it — "std::erase_if, C++20". Never guess a fact you are unsure of; "I believe it is X, but I would check cppreference" is a fine answer and a confident wrong one is not. When you do not know, connect it to what you do: "I have not used std::pmr, but allocators are a template parameter, so I would expect it to swap the allocation strategy."
Key takeaways
- Thirty questions cover almost every C++ theory round; know the definition-plus-consequence answer to each and which module explains it.
- The mechanisms interviewers listen for: RAII, the rule of five and zero,
std::moveas a cast, the vtable, reallocation invalidating references, a data race as undefined behaviour. - Expect the follow-up: the overload-versus-override snippet, the moved-from string, the single-bucket hash, the constructor's virtual call.
- Name the mechanism and the standard version; never bluff; connect the unknown to the known.
Common questions
What is RAII in C++?
RAII — Resource Acquisition Is Initialisation — ties a resource's lifetime to an object's: the constructor acquires it and the destructor releases it, and the destructor runs on every exit from the scope, including exceptions and early returns. std::vector, std::unique_ptr, std::lock_guard and std::ofstream are all RAII types, which is why C++ needs no finally.
What does std::move actually do?
Nothing at run time: std::move is a cast to an rvalue reference, which lets overload resolution choose the move constructor or move assignment. The move happens there, leaving the source valid but unspecified — fit to assign to or destroy. std::move on a const object silently selects the copy.
What is the rule of five in C++?
A class that manages a resource and declares any of the destructor, copy constructor, copy assignment, move constructor or move assignment usually needs all five. A user-declared destructor or copy operation suppresses the implicit moves, so the class silently copies where it could move. The rule of zero avoids all five by letting members such as std::vector and std::unique_ptr own the resources.
How does a virtual function call work in C++?
Each class with virtual functions has a table of function pointers, the vtable, and each object carries a hidden pointer to its class's table. A call through a base pointer or reference loads that pointer, indexes the table and calls indirectly, so the function is chosen by the object's dynamic type.
Why must a base class destructor be virtual?
Deleting a derived object through a base pointer whose destructor is not virtual is undefined behaviour: in practice only the base destructor runs and the derived part is never destroyed. Any class meant to be deleted through a base pointer declares virtual ~Base() = default;.
What is the difference between unique_ptr and shared_ptr?
std::unique_ptr is exclusive ownership: one pointer wide, move-only, and it frees the object in its destructor — the default choice. std::shared_ptr shares ownership through a reference-counted control block; copies are allowed, each costs an atomic increment, and the object is freed when the last owner goes. std::make_shared puts the object and the control block in one allocation.
Exercise
Static versus dynamic dispatch
Write struct Animal with a virtual destructor, virtual std::string speak() const returning ..., and a non-virtual std::string kind() const returning animal. Derive Dog (speak overrides to Woof; kind is redeclared to return dog — hiding, not overriding) and Cat (Meow, cat). Add three free overloads describe(const Animal&), describe(const Dog&), describe(const Cat&) returning describe(Animal), describe(Dog) and describe(Cat).
Read n and n words dog/cat. For each, create the animal in a std::unique_ptr<Animal> and print one line:
<word>: speak=<a->speak()> describe=<describe(*a)> cast=<describe(...) after a dynamic_cast to the real type> kind=<a->kind()> sliced=<speak() of an Animal copy-constructed from *a>
Predict before running: the override is chosen at run time, the overload at compile time, the non-virtual call by the static type, and the slice keeps only the base.
2
dog cat
prints
dog: speak=Woof describe=describe(Animal) cast=describe(Dog) kind=animal sliced=...
cat: speak=Meow describe=describe(Animal) cast=describe(Cat) kind=animal sliced=...In this module: Interview idioms
- The interview template — the contest idioms and what they cost
- The STL idiom sheet — the two-line answers to the common shapes
- The pitfalls that fail interviews
- Implement the built-in — a vector, a unique_ptr, a hash map and a string by hand
- Writing clean solutions under time pressure
- The C++ theory drill — thirty questions, thirty answers (this lesson)
- Final checkpoint — Interview idioms
← Writing clean solutions under time pressure · Final checkpoint — Interview idioms →