Interview Prep — C++ Track

C++ Interview Questions

15 C++ questions — references vs pointers, RAII, smart pointers, the rule of five, virtual functions and the vtable, and move semantics. The memory-model depth systems and quant interviewers dig into. Tagged for FAANG and startups.

CoreMemoryAdvanced
30Total
5Core
5Memory
5Advanced
Progress saved locally. Check off questions — saves to your browser. No account needed.
0
/ 15 Done
01References vs pointers — what's the difference?C++▾
ReferencePointer
NullCannot be nullCan be null
ReassignBound once at initCan point elsewhere
Init requiredYesNo
ArithmeticNoYes (p+1)
SyntaxActs like the objectNeeds * / ->
Guidance: prefer references for function parameters that must always refer to a valid object; use pointers when the target is optional (can be null) or must be re-pointed.
02Pass by value vs reference vs const reference?C++▾
  • By value — copies the argument; caller unaffected. Fine for small types (int, double).
  • By reference (T&) — no copy; the function can modify the caller's object.
  • By const reference (const T&) — no copy, cannot modify. The default for large objects.
cpp
void print(const std::string& s);  // no copy, read-only
void sort(std::vector<int>& v);      // mutates caller's vector
03What does const correctness mean?C++▾
Using const everywhere the data should not change — parameters, member functions, and pointers. It documents intent, lets the compiler catch accidental mutation, and enables optimizations.
cpp
int size() const;          // method doesn't modify the object
const int* p;             // pointer to const int (can't change *p)
int* const q = &x;        // const pointer (can't repoint q)
Read right-to-left: const int* p = "p is a pointer to a const int"; int* const q = "q is a const pointer to an int".
04struct vs class in C++?C++▾
The only language-level difference: members and inheritance are public by default in a struct, and private by default in a class. Both can have constructors, methods, access specifiers, and inheritance.
Convention: use struct for passive data aggregates (plain fields), class for types with invariants and behaviour. It's a readability signal, not a technical one.
05Compile-time polymorphism vs runtime polymorphism?C++▾
  • Compile-time (static) — function/operator overloading and templates. Resolved by the compiler; zero runtime cost.
  • Runtime (dynamic) — virtual functions dispatched through the vtable, based on the object's actual type at run time.
C++ gives you both, and templates make static polymorphism especially powerful (the STL is built on it).
06Overloading, overriding and hiding — three different things. Which is which?C++▾

Overloading is two functions in the same scope with the same name and different parameter lists; the compiler picks one at the call site. Overriding replaces a virtual base function in a derived class with the same signature, resolved through the vtable at runtime. Hiding is the accident: a derived member with the same name but a different signature hides every base overload of that name.

The trap: hiding looks like overriding until a call fails to compile. using Base::f; in the derived class brings the base overloads back into scope, and override makes the compiler reject a mismatched signature outright.
07What does explicit do on a constructor?C++▾

It blocks implicit conversion. A single-argument constructor without explicit doubles as a conversion operator, so the compiler will silently build your type to make a call work.

struct Buffer {
  Buffer(int size);          // implicit
  explicit Buffer(int size); // opt out
};

void send(Buffer b);
send(42);   // compiles with the first; rejected with the second

Mark single-argument constructors explicit by default. Leave it off only when the conversion is genuinely something you want at every call site, such as std::string from a string literal.

08Lvalue vs rvalue — how do you tell them apart?C++▾

An lvalue has an identity you can take the address of: a named variable, a dereferenced pointer, a function returning T&. An rvalue is a temporary with no name: a literal, an arithmetic result, a function returning by value.

int  x = 5;   // x is an lvalue, 5 is an rvalue
int& a = x;   // fine: lvalue reference binds to an lvalue
int& b = 5;   // error
int&& c = 5;  // fine: rvalue reference binds to a temporary
const int& d = 5; // also fine: const lvalue ref extends the temporary

The distinction is what move semantics is built on: if the compiler can prove an argument is an rvalue, its contents can be stolen rather than copied.

09static has three different meanings in C++. Name them.C++▾

At file scope it gives a variable or function internal linkage, so it is private to that translation unit. Inside a function it makes a local persist across calls, initialised once on first use and destroyed at exit. Inside a class it makes a member belong to the class rather than any object, shared by every instance and callable without one.

Function-local statics have been thread-safe to initialise since C++11 — the compiler emits a guard. That is what makes the Meyers singleton safe without a mutex.
10Why do headers need include guards, and what does #pragma once change?C++▾

#include is textual: without a guard, a header pulled in twice pastes its declarations twice, and redefinition is an error. A guard makes the second inclusion expand to nothing.

#ifndef WIDGET_H
#define WIDGET_H
// ... declarations ...
#endif

// or, one line:
#pragma once

#pragma once does the same job keyed on the file identity rather than a macro name, so it cannot collide and cannot be misspelled. It is not in the standard, but every mainstream compiler supports it.

11Stack vs heap — how do they differ?C++▾
StackHeap
AllocationAutomatic (scope)Manual (new) or smart ptr
SpeedVery fast (pointer bump)Slower (allocator)
SizeSmall, fixedLarge
LifetimeEnds at scope exitUntil freed
RiskOverflow (deep recursion)Leaks, fragmentation
Prefer the stack and RAII objects. Reach for the heap only when an object must outlive its scope or is too large for the stack.
12What is RAII?C++▾
RAII (Resource Acquisition Is Initialization) ties a resource's lifetime to an object's scope: acquire in the constructor, release in the destructor. When the object goes out of scope — even via an exception — the destructor runs and cleans up automatically.
cpp
{
    std::lock_guard<std::mutex> lk(m);  // locks here
    // ... critical section ...
}   // unlocks automatically at scope end
RAII is the foundation of smart pointers, file streams, and lock guards. It's why idiomatic C++ rarely needs manual cleanup.
13Compare unique_ptr, shared_ptr, and weak_ptr.C++▾
  • unique_ptr — sole ownership, no overhead, move-only. The default choice.
  • shared_ptr — shared ownership via an atomic reference count; freed when the count hits zero.
  • weak_ptr — a non-owning observer of a shared_ptr; used to break reference cycles.
cpp
auto u = std::make_unique<Widget>();   // one owner
auto s = std::make_shared<Widget>();   // ref-counted
std::weak_ptr<Widget> w = s;            // observes, doesn't own
Cycle trap: two shared_ptrs pointing at each other never reach count zero — a leak. Make one direction a weak_ptr.
14What is a dangling pointer, and how do you avoid one?C++▾
A dangling pointer points at memory that has been freed or gone out of scope. Dereferencing it is undefined behavior — often a silent corruption rather than a clean crash.
cpp
int* p = new int(5);
delete p;
*p = 10;   // UB — p dangles
Avoid it by: setting freed pointers to nullptr, never returning addresses of locals, and using smart pointers so ownership and lifetime are explicit.
15new/delete vs malloc/free?C++▾
new / deletemalloc / free
ConstructorsCalledNot called (raw bytes)
ReturnsTyped pointervoid*, needs cast
On failureThrows bad_allocReturns NULL
SizeComputed automaticallyYou compute bytes
In C++ prefer smart pointers over raw new/delete, and never mix the two families (don't free a new'd pointer).
16Why does deleting through a base pointer need a virtual destructor?C++▾

Without one, delete on a Base* runs only ~Base. The derived part of the object is never destroyed, so anything it owns leaks — and the standard calls this undefined behaviour, not merely a leak.

struct Base { ~Base(); };                  // not virtual
struct Derived : Base { std::vector<int> data; };

Base* p = new Derived;
delete p;   // ~Derived never runs; data leaks
Rule: if a class has any virtual function, give it a virtual destructor. If it is not meant to be deleted polymorphically, make the destructor protected and non-virtual to say so.
17What is a shared_ptr cycle, and how does weak_ptr break it?C++▾

Two objects holding shared_ptrs to each other keep each other’s reference count at one forever. Neither is ever destroyed, and reference counting cannot detect it — there is no garbage collector to notice the island.

struct Node {
  std::shared_ptr<Node> next;   // parent -> child: ownership
  std::weak_ptr<Node>   prev;   // child -> parent: observation
};

Make one direction non-owning. A weak_ptr does not raise the count; you call lock() to get a shared_ptr when you need the object, and get an empty one if it is already gone. Parent-child and observer links are the usual places this shows up.

18You suspect a leak. How do you actually find it?C++▾

Reproduce under a tool rather than reading code. AddressSanitizer (-fsanitize=address) is built into Clang and GCC and reports leaks with allocation stacks at exit. Valgrind’s memcheck needs no rebuild but runs far slower. On Windows, CRT debug heap or Application Verifier.

A leak that grows with load is the one that matters. A single allocation held for the process lifetime is not a bug worth chasing — measure whether the number rises across repeated cycles of the same operation.

The structural fix is almost always ownership: a raw new with no owner becomes a unique_ptr, and the leak class disappears rather than getting patched.

19What does std::move actually do?C++▾

Nothing at runtime. It is a cast: it turns its argument into an rvalue reference so that overload resolution picks the move constructor or move assignment instead of the copying one. The moving is done by that constructor, not by move itself.

std::string a = "hello";
std::string b = std::move(a);   // b steals a's buffer
// a is valid but unspecified: safe to assign or destroy, not to read
Careless move costs nothing and gains nothing: moving a const object silently copies, because a move constructor cannot take a const&&. Moving a local you then return also defeats copy elision.
20Why is vector::push_back amortised O(1), and what does it invalidate?C++▾

A vector grows geometrically — typically doubling. Copying n elements happens only on the reallocations, and the doubling makes the total copying across n pushes O(n), so the cost per push averages out to a constant even though individual pushes can be O(n).

Reallocation invalidates everything: all iterators, pointers and references into the vector. A loop holding an iterator while pushing is a use-after-free. reserve() up front avoids both the copies and the invalidation.

push_back that does not reallocate invalidates only the end iterator. Erasing invalidates from the erase point onward.

21Explain the rule of three / five / zero.C++▾
  • Rule of Three: if you define any of destructor, copy constructor, or copy assignment, you probably need all three (they manage a resource).
  • Rule of Five: with move semantics (C++11), add the move constructor and move assignment.
  • Rule of Zero: best of all — design classes so members (smart pointers, containers) manage resources, and you define none of the five.
Modern goal: aim for the rule of zero. Let vector, string, and smart pointers handle resources so the compiler-generated special members just work.
22How do virtual functions and the vtable work?C++▾
A class with virtual functions gets a hidden vtable (array of function pointers). Each object stores a vptr to its class's vtable. A virtual call looks up the function through the vptr at runtime, so the derived override runs even through a base pointer.
cpp
Base* p = new Derived();
p->speak();   // Derived::speak via vtable dispatch
Critical: a polymorphic base class needs a virtual destructor. Deleting a Derived through a Base* without one only runs ~Base, leaking the derived part.
23What are move semantics and rvalue references?C++▾
Move semantics (C++11) let you transfer resources out of a temporary instead of copying them. An rvalue reference (T&&) binds to temporaries, and std::move casts an lvalue to one so its resources can be stolen.
cpp
std::vector<int> a = {1, 2, 3};
std::vector<int> b = std::move(a);  // steals a's buffer, no copy
// a is now valid but unspecified (usually empty)
This turns an O(n) deep copy of a large container into an O(1) pointer swap.
24Shallow copy vs deep copy?C++▾
A shallow copy copies pointer members as-is, so two objects share (and both try to free) the same memory — a double-free crash. A deep copy duplicates the pointed-to data so each object owns its own.
Why it matters: the compiler-generated copy constructor does a member-wise (shallow) copy. If your class owns a raw pointer, you must write a deep-copying copy constructor and assignment (the rule of three) — or use a smart pointer / container and get the rule of zero.
25What are templates? How do they differ from Java generics?C++▾
Templates generate a separate, specialized version of a function or class for each type used, at compile time, with zero runtime overhead.
cpp
template<typename T>
T maximum(T a, T b) { return a > b ? a : b; }
maximum(3, 7);      // stamps out int version
maximum(2.5, 1.5);  // stamps out double version
vs Java generics: C++ templates are reified — the type survives and each instantiation is real, optimized code. Java erases generic types at compile time, so List<String> is just List at runtime.
26const vs constexpr — what is the real difference?C++▾

const means "this will not be modified after initialisation" — the value may still be computed at runtime. constexpr means "this can be computed at compile time", which is a stronger claim and lets the value be used where the language demands a constant, such as an array size or a template argument.

const     int a = read();  // fine: runtime value, just immutable
constexpr int b = read();  // error: not a constant expression
constexpr int c = 4 * 8;   // fine
std::array<int, c> arr;    // c usable as a template argument; a is not

On a function, constexpr says it may run at compile time when its arguments allow; it still works as a normal function otherwise.

27What is copy elision, and why should you not return std::move(x)?C++▾

Copy elision lets the compiler construct a returned object directly in the caller’s storage, skipping the copy or move entirely. Since C++17 it is guaranteed for returning a temporary, and named return value optimisation covers most named locals too.

std::string make() {
  std::string s = build();
  return s;             // elided: constructed in place
  // return std::move(s); // forces a move, defeating elision
}
Wrapping the return in std::move turns the expression into an rvalue reference, which is no longer eligible for elision. You pay for a move you would otherwise not have made.
28Name three things that are undefined behaviour in C++, and why UB is worse than a crash.C++▾

Reading an uninitialised variable; indexing past the end of an array; dereferencing a null or dangling pointer; signed integer overflow; data race on a non-atomic; deleting through a base pointer with a non-virtual destructor.

The compiler is allowed to assume UB never happens, and optimises on that assumption. A null check after a dereference can be deleted as dead code, because the dereference proved the pointer non-null. That is why UB shows up as a wrong answer in an unrelated function rather than a crash at the fault.

Build with -fsanitize=address,undefined in CI. Most of this class of bug is caught the first time the line executes.
29How do you branch on a type at compile time?C++▾

if constexpr (C++17) discards the untaken branch before it is instantiated, so a branch that would not compile for the current type is simply not there.

template <typename T>
void print(const T& v) {
  if constexpr (std::is_pointer_v<T>) std::cout << *v;  // only for pointers
  else                                std::cout <<  v;
}

Before C++17 the same job needed tag dispatch or SFINAE — writing an overload whose signature is only valid for certain types via std::enable_if, so invalid candidates drop out of overload resolution instead of erroring. C++20 concepts express the same constraint readably.

30Lambda captures: by value or by reference, and what is the trap?C++▾

[x] copies, [&x] binds a reference, [=] and [&] do either for everything used. [x = std::move(v)] initialises a capture directly (C++14), which is how you move something into a lambda.

The trap: a reference capture outlives the scope it referenced. A lambda that captures a local by reference and is then stored in a member, queued on a thread pool, or returned, is holding a dangling reference the moment the function returns.

If the lambda escapes the enclosing scope in any way, capture by value — or capture a shared_ptr so the referent stays alive as long as the lambda does.

No questions match your search.