C++ Interview Prep · Part 1
Your course notes rebuilt: wrong answers fixed, deprecated material replaced, and the missing high-yield topics added — RAII, exception guarantees, lambdas, the four casts, static, and the rapid-fire classics. Targets C++17/20.
Q: What constrains the performance of a computing system? Hardware efficiency, software/algorithmic efficiency, communication/interface overhead, processing power, memory/storage. (Interview add-on: cache behavior and memory bandwidth often dominate in practice.)
Q: Imperative vs declarative paradigm? Imperative: a program is a sequence of steps that mutate state. Declarative: you describe what to compute, not how (SQL, functional style).
Q: Two aspects of an object? State (member variables) and behavior (member functions).
Q: Objects vs classes? A class defines the structure/blueprint; objects are instances of a class.
Q: Three pillars of OOP?
Q: Can you write imperative code in an OOP language? Yes. Paradigm is a style, not a language feature.
Q: struct vs class in C++? The only differences: struct members and inheritance default to public; class defaults to private. Otherwise identical — structs can have methods, constructors, virtual functions, everything.
Q: Can different struct types share member variable names? Yes. Names are scoped to the type.
Q: . vs -> vs ::?
. — member access on an object (NOT a dereference).-> — dereference + member access on a pointer: p->x ≡ (*p).x.:: — scope resolution operator (namespaces, class statics, base-class members).Don't call . a "scope operator" in an interview — it's a member access operator.
Q: Block / local / global scope? Global: declared outside all functions/classes. Local: inside a block {}. Every local scope is a block scope; globals live at namespace scope.
Q: Can you declare a struct inside a struct? Yes — nested types. Access via Outer::Inner.
Q: private vs protected vs public?
private — accessible only by the class's own members and friends.protected — also accessible by derived classes.public — accessible by anyone.Correction from your notes: access control is per-class, not per-object. A member function of Foo can access private members of any Foo object, not just *this. That's why copy constructors can read other's private fields.
Q: If all members are private, what's the problem? Nothing outside the class can use it except friends. You need public member functions (or friends) to form an interface.
Q: How do you access an object's state from outside? Public member functions (accessors/mutators) — or public members, but prefer a minimal public interface.
Q: What are friend functions? Do they break encapsulation? A friend declaration grants a non-member function (or another class) access to private/protected members. They don't break encapsulation if they're part of the class's designed interface — the class author opts in.
Corrections from your notes:
public:/private:) where the friend line appears is irrelevant — friendship isn't subject to access specifiers.operator<<, since the left operand is ostream, not your class.Q: If the class implementation changes, do friends need to change? Possibly — friends depend on internals, which is why you keep them few.
Q: What initializes an object at creation? A constructor. It's a member function with constraints: same name as the class, no return type (not even void).
Q: What's called when you write Foo f;? The default constructor. If you declare any constructor, the compiler stops generating the default one (bring it back with Foo() = default;).
Q: Why is Foo f(); wrong? Most vexing parse — it declares a function f returning Foo. Use Foo f; or Foo f{}; (brace init, C++11, also prevents narrowing).
Q: Member initializer list?
Foo::Foo(int n, std::string s) : n_(n), s_(std::move(s)) {}
Initializes members directly instead of default-constructing then assigning. Required for const members, references, and members without default constructors. Members initialize in declaration order, not list order. Values can be parameters or any expression.
(Terminology: "member initializer list" — not std::initializer_list, which is the {1,2,3} container thing.)
Q: Constructor delegation and its limitation?
Foo::Foo() : Foo(0) {} // delegates to Foo(int)
Limitation: a delegating constructor cannot also have a member initializer list — delegation must be the only thing before the body.
Q: What does Foo(1, 2) as an expression do? Constructs a temporary (anonymous) object — it does not "re-run" the constructor on an existing object.
Q: What is an abstract data type (ADT)? A type where users interact only through its interface; representation and implementation are hidden. Not every user-defined type is an ADT — only if internals are inaccessible.
Q: How do you separate interface from implementation? Header (.h) declares the interface; source (.cpp) defines it. (Exception: templates — see §8.)
Q: What is a destructor? ~Foo() — runs automatically when the object's lifetime ends (scope exit, delete, container removal). No parameters, no return type, one per class.
Correction: destructors can be called explicitly (obj.~Foo();) — it's just almost never done outside placement-new code. Say "shouldn't," not "can't."
Q: What is a copy constructor and why do you need one? Foo(const Foo& other) — constructs a new object from an existing one. The compiler-generated one does memberwise copy ("shallow" only matters when members are raw pointers). If your class owns a raw resource, the default copy leaves two objects pointing at one resource → double delete.
Q: When must you overload the assignment operator? Same situation — owning raw resources. And you must handle self-assignment (a = a), or you'd free the resource before copying from it:
Foo& operator=(const Foo& other) {
if (this != &other) { /* free, deep copy */ }
return *this;
}
Q: Rule of Three / Five / Zero?
Foo(Foo&& other) noexcept;
Foo& operator=(Foo&& other) noexcept;
std::vector, std::string, std::unique_ptr as members and write none of them — the compiler-generated ones are correct. This is the answer interviewers want.Q: What is std::move? A cast to rvalue reference — it doesn't move anything itself, it permits moving. After moving from an object, it's in a valid-but-unspecified state.
Q: = default and = delete?
Foo(const Foo&) = delete; // non-copyable
Foo& operator=(const Foo&) = delete;
Foo() = default; // restore compiler-generated
Q: new/delete vs malloc/free? Both allocate from the heap/free store, but new calls constructors and delete calls destructors; malloc/free don't. Never mix them. In modern code, avoid raw new/delete entirely:
Q: Smart pointers?
std::unique_ptr<T> — sole ownership, zero overhead, movable not copyable. Default choice. auto p = std::make_unique<T>(args);std::shared_ptr<T> — reference-counted shared ownership. std::make_shared<T>(args). Costs atomic refcount ops.std::weak_ptr<T> — non-owning observer of a shared_ptr; breaks reference cycles.Writing raw new/delete in interview code (outside implementing a data structure that requires it) is a red flag.
Q: What constructor runs for Foo arr[10];? The default constructor, for each element.
Q: Why is pass-by-reference more efficient than by-value? By-value copies the argument (constructor + destructor cost); by-reference aliases the original. Idiom: const T& for read-only, T& to modify, plain T for cheap types (int, pointers, string_view, iterators) or when you'd copy anyway (then move from it).
Q: What does const do?
const T& param — callee can't modify the argument.void f() const; — member function can't modify member variables (callable on const objects).constexpr for compile-time constants.Q: What is coercion? Implicit conversion — C++ auto-converts in well-defined cases (int→double, derived*→base*, single-arg constructors). Mark single-arg constructors explicit to prevent surprise conversions.
Q: Why do classes provide const and non-const overloads of the same function?
T& operator[](size_t i) { return data_[i]; }
const T& operator[](size_t i) const { return data_[i]; }
A const object can only call const member functions, so without the second overload const vector couldn't be indexed at all. The const one returns a const reference so callers can't mutate through it. This pair is the canonical "const correctness" example.
Q: What is mutable? Lets a member be modified inside const member functions — for members that aren't part of the object's logical state: caches, memo tables, mutexes (mutable std::mutex m_; so get() const can lock). The distinction to name: bitwise vs logical constness — const promises the observable state won't change.
Q: References vs pointers? A reference must be initialized, can't be null, and can't be reseated to refer to something else; a pointer can be all three. References for "always refers to something" parameters; pointers when null/"absent" is meaningful or reseating is needed. (Under the hood a reference is typically a pointer — the difference is the rules the compiler enforces.)
Q: The four casts — when is each right?
static_cast — the normal one: numeric conversions, up/down class hierarchy when you know the type, void*→T*. No runtime check.dynamic_cast — checked downcast on polymorphic types; nullptr/throws on failure. Runtime cost.const_cast — add/remove const. Legitimate only for interop with const-incorrect APIs; writing through it to a originally-const object is UB.reinterpret_cast — reinterpret the bits (pointer↔integer, unrelated pointer types). Almost always paired with UB risk (strict aliasing); rare and deliberate.(T)x try all of the above in order — they can silently do a reinterpret_cast or strip const, which is exactly why they're banned in modern codebases. "Greppable and intentional" is why the named casts exist.Q: What is operator overloading? Defining operators for user-defined types, as member functions or free (often friend) functions. Symmetric binary ops (+, ==) are usually free functions; =, [], (), -> must be members.
Q: Why does operator<< return ostream& (not ostream)? Two real reasons (your notes' answer was wrong): 1. Streams are non-copyable — returning by value wouldn't compile. 2. Returning the reference enables chaining: cout << a << b is operator<<(operator<<(cout, a), b).
std::ostream& operator<<(std::ostream& os, const Foo& f) {
return os << f.x();
}
Q: How do you make objects comparable? Classic: overload ==, <, etc. C++20: default the spaceship operator and get all six for free:
auto operator<=>(const Foo&) const = default; // also generates ==
Q: Do derived classes contain all base members? Yes — including private ones — but private base members aren't directly accessible from the derived class (they're still there in memory, reachable via protected/public base functions).
Q: What's inherited access-wise with public inheritance? public→public, protected→protected, private→inaccessible. Protected pros/cons: derived classes get direct access, but that couples every descendant to the representation.
Q: What isn't inherited? Constructors (but using Base::Base; inherits them since C++11), assignment operators (redeclared per class), destructors, friends.
Q: Base constructor invocation from derived?
Derived::Derived(int n) : Base(n), extra_(0) {}
If you don't call one explicitly, the base's default constructor runs. Base members are initialized by the base constructor.
Q: Redefining vs overriding vs overloading?
virtual. Dynamically bound through pointers/references.Call the hidden/overridden base version with obj.Base::func();.
Q: What is polymorphism mechanically in C++? Virtual functions + late (dynamic) binding: the call through a Base*/Base& dispatches to the most-derived override at runtime (via vtable).
Q: Always use override (C++11):
struct Derived : Base {
void f() override; // compiler error if it doesn't actually override
void g() final; // no further overriding
};
Not marking overrides is a code-review red flag; a signature typo silently hides instead of overrides.
Q: Why not make everything virtual? Virtual calls cost an indirection and block inlining; objects grow a vptr. Only pay for dynamic dispatch where you need it.
Q: Pure virtual functions / abstract classes?
struct Shape { virtual double area() const = 0; virtual ~Shape() = default; };
A class with any pure virtual function is abstract — can't be instantiated; derived classes must implement it. This replaces the old "stub bodies for virtuals" hack in your notes.
Q: What does "a derived object has more than one type" mean? A Derived is-a Base: usable anywhere a Base (pointer/reference) is expected. Never the reverse.
Q: What is the slicing problem? Assigning/passing a derived object by value as a base copies only the base subobject — derived data and dynamic behavior are sliced off. Avoid by using pointers/references (or smart pointers) to base. This is why polymorphism requires indirection.
Q: Why must destructors be virtual in polymorphic base classes? delete basePtr; where basePtr points to a Derived invokes only ~Base() if it's non-virtual → undefined behavior / leaked derived resources. Rule: any class with virtual functions gets virtual ~Base() = default;.
Q: If the base overloads operator= but the derived doesn't? The derived class gets its own compiler-generated assignment, which calls the base's overloaded one for the base subobject.
Q: Can you forward-declare a template class? Yes (your notes were wrong): template <typename T> class Foo; is valid.
Q: Why can't templates live in separate .cpp files (normally)? The compiler must see the full template definition at each instantiation point to generate code for that T. So template definitions go in headers (or you use explicit instantiation for a fixed set of types).
Q: typedef vs using? Prefer using (C++11):
using NodePtr = Node*; // same as typedef
template <class T> using Vec = std::vector<T>; // alias template — typedef can't do this
Q: What actually happens when a template is used? The compiler instantiates it — stamps out real code for that exact T, per translation unit (the linker deduplicates). Consequences worth naming: each distinct T is a separate compiled function (code size and compile time scale with instantiation count — a real cost in big codebases), errors surface at instantiation point (concepts in C++20 exist to move them to the call site with readable messages), and specialization exists — you can provide a hand-written version for a specific type (template<> struct Hash<MyKey> {...}, which is exactly how std::hash is extended for your own key types).
Q: Compiler vs linker? Compiler translates each translation unit (.cpp after preprocessing) into object code. Linker combines object files + libraries into an executable, resolving symbols. "It compiles but won't link" = declaration existed (header) but no definition was linked in.
Q: #include "foo.h" vs #include <vector>? Quotes: search local/project dirs first (your headers). Angle brackets: system/library include paths. #include is preprocessor text substitution — which is why including a header satisfies the compiler but doesn't provide linked definitions.
Q: Why separate compilation? Header reuse across files; faster incremental builds — only changed .cpp files recompile. Headers need include guards or #pragma once.
Q: What is a namespace? A named scope grouping declarations. Multiple namespaces fine; same function name in different namespaces fine — qualify (ns::f()) or use a using-declaration (using ns::f;) to disambiguate. Avoid using namespace std; in headers (pollutes every includer).
Q: Unnamed (anonymous) namespace?
namespace { void helper(); } // internal linkage
Visible only within its translation unit — the modern replacement for file-static helpers, and why you use it: no name collisions with other .cpp files. Access a shadowed global with ::name.
Q: NULL vs nullptr? NULL is an integral constant (typically 0) — it can select f(int) over f(char*) in overload resolution. nullptr has its own type std::nullptr_t and converts only to pointer types. Always use nullptr. (No using-directive needed for either; NULL is a macro.)
Q: What does static mean? (depends where — classic rapid-fire question)
this.Q: What is the ODR, and what does inline really mean now? ODR = one definition rule: one definition of each entity per program (one per translation unit for inlinable things, and they must be identical). inline today means "multiple identical definitions across translation units are allowed — linker, keep one" — it is not a request to inline (optimizers decide that themselves). It's why function bodies in headers need inline (or to be templates/member functions, which get it implicitly), and C++17 inline variables let you define globals in headers without link errors.
Q: What is the static initialization order fiasco? The initialization order of globals in different translation units is unspecified — a global whose constructor uses another TU's global may run first and read garbage. Fix: function-local static (Widget& instance() { static Widget w; return w; }) — constructed on first use, thread-safe. This is why the Meyers singleton looks the way it does.
Q: Error handling vs exception handling? Error handling: check and branch on failure conditions (return codes, std::optional, std::expected in C++23). Exception handling: throw transfers control to a matching catch, unwinding the stack (destructors run — this is RAII's payoff).
Q: Throw/catch mechanics?
try {
if (bad) throw std::runtime_error("what happened");
} catch (const std::exception& e) { // catch by const ref!
log(e.what());
} catch (...) { // catch anything
}
std::exception.const& — catching by value slices derived exception types.std::terminate.Q: What replaced throw lists / exception specifications? throw(e1, e2) specs were deprecated in C++11 and removed in C++17. Your notes' rules about matching throw lists on overloads/overrides are obsolete. Modern C++:
void f() noexcept; // promises not to throw; violating it -> std::terminate
Mark move constructors/assignment noexcept — containers like vector only move elements during reallocation if the move is noexcept (otherwise they copy for the strong guarantee).
Q: When to terminate vs recover? Unrecoverable/corrupt state: propagate or terminate. Recoverable (bad user input, transient failure): catch at the level that can retry or substitute data.
Q: What is RAII? (the most-asked C++ concept, full stop) RAII = resource acquisition is initialization: tie a resource's lifetime to an object's lifetime — acquire in the constructor, release in the destructor. Because destructors run on every scope exit (return, exception, break), cleanup becomes unskippable. lock_guard, unique_ptr, fstream, vector are all RAII. It's also why C++ exceptions are usable at all: stack unwinding runs destructors, so RAII code is exception-safe by construction while malloc/lock() code leaks on the first throw.
Q: The three exception safety guarantees?
noexcept): destructors, moves, swaps should live here.vector::push_back gives this — which is exactly why reallocation copies non-noexcept-movable types.Being able to classify a function you just wrote into one of these is a senior-level tell.
Q: throw; vs throw e; inside a catch block?
catch (const std::exception& e) {
log(e.what());
throw; // rethrows the ORIGINAL object, dynamic type intact
// throw e; // copies e AS ITS STATIC TYPE — slices a derived exception!
}
Bare throw; is the only correct rethrow. throw e; is the slicing problem wearing an exception costume.
Q: Can a destructor throw? Destructors are implicitly noexcept. If one throws during stack unwinding (an exception already in flight), the program calls std::terminate — two exceptions can't propagate at once. Rule: destructors never throw; if cleanup can fail, offer an explicit close() that can, and have the destructor swallow or log.
Q: What is a lambda, mechanically? Compiler-generated class with captured variables as members and operator() as the body (a "closure object"). Knowing this makes every rule below derivable.
int base = 10;
auto add = [base](int x) { return base + x; }; // capture by value: member copy
auto add2 = [&base](int x) { return base + x; }; // capture by reference: member ref
Q: The capture traps?
[&] capturing locals, lambda outlives the scope (stored callback, thread, async). Rule: by value (or move) for anything that escapes the current scope; [&] only for lambdas consumed immediately (e.g. passed to std::sort).this is captured as a pointer — both [this] and [=] capture the pointer, not the object; if the object dies before the callback runs, dangling. [*this] (C++17) copies the object; capturing a shared_ptr/weak_ptr to self is the async-code pattern.const inside the lambda unless you add mutable (which makes operator() non-const — it mutates the closure's copy, never the original).[buf = std::move(buffer)] { ... } — the way to hand a resource to a callback without copying.Q: Lambda vs std::function vs function pointer?
std::sort) keeps its exact type → the call inlines. Passing through std::function type-erases: possible heap allocation for large captures + an indirect call that blocks inlining. Rule for hot paths: template/auto parameters, not std::function. (This is also the qsort-vs-std::sort answer: sort's inlined comparator beats qsort's function pointer.)enum class vs enum: scoped (must qualify Color::Red), doesn't implicitly convert to int, doesn't leak names into the enclosing scope, and you can fix the underlying type. Always enum class in new code.auto pitfalls: auto deduction drops references and top-level const — auto x = getRef(); copies. Want the reference? auto&. Read-only loop over expensive elements: for (const auto& e : v) — plain for (auto e : v) copies every element, a classic silent cost.++i vs i++: identical for ints after optimization; for iterators, i++ must materialize a copy of the old value — habit: pre-increment in loops.== vs .equals-style traps from other languages: C++ == on pointers compares addresses; on containers/strings compares contents. Know which one you're holding.size_t underflow: for (size_t i = n - 1; i >= 0; --i) never terminates — unsigned can't go negative. Loop down with for (size_t i = n; i-- > 0;) or use a signed index.-Wshadow exists because constructors with parameter names matching members are the classic case (x = x; assigns the parameter to itself — use the member init list).Linked list: dynamic size, O(n) access, O(1) insert/remove given the node. Self-referential via a pointer member (legal because a pointer's size is known before the type is complete). Doubly linked: nodes carry prev + next; keep head and tail. Binary tree node: data + left + right. Any node-based structure owning raw pointers needs the Rule of Three/Five — or hold children in unique_ptr and get Rule of Zero.
typedef in old list code → using Node = struct node; style aliases now.
Recursion: must have (1) reachable base case(s) — guard with a condition and make progress toward it each call, (2) correct base case behavior, (3) correct recursive step. Each call pushes a stack frame → deep recursion risks stack overflow; iterative versions trade code brevity for less memory/overhead. (Interview note: know how to convert recursion to iteration with an explicit stack, and that tail calls aren't guaranteed to be optimized in C++.)
Binary search: on a sorted range, compare the middle element, recurse/iterate into the half that can contain the target. O(log n). In C++: std::lower_bound / std::upper_bound / std::binary_search. Classic bug: mid = lo + (hi - lo) / 2 to avoid overflow.
assert(cond) from <cassert>; disabled when NDEBUG is defined before the include — in practice via the compiler flag -DNDEBUG (standard in release builds).nullptr, never NULL/0using aliases, not typedefoverride on every overriding virtual; virtual ~Base() = default; in polymorphic basesunique_ptr/make_unique instead of raw new/deletestd::move for transfers; move ops marked noexceptexplicit on single-argument constructors{}; beware Foo f(); most vexing parsefor (const auto& x : v) — plain auto copiesconst& parameters for non-cheap read-only args; const/non-const member overload pairsconst&; rethrow with bare throw;; destructors never throwstd::function in hot pathsenum class, static in its three meanings, Meyers singleton for init-order problems<=> defaulted comparisons, std::span, concepts (recognize, not necessarily write)