OOP
Virtual dispatch, vtable layout, object slicing, Rule of Five, and polymorphic design.
60 questions
JuniorTheoryVery commonWhat is an abstract class and why is it needed?
What is an abstract class and why is it needed?
An abstract class has at least one pure virtual function (= 0); it cannot be instantiated directly and serves as a contract for derived classes. In C++ a pure virtual function may still have a body — useful for the destructor.
Common mistakes
- ✗Trying to instantiate an abstract class directly — compiler error
- ✗Not providing a virtual destructor in the abstract base class — leads to partial destruction via base pointer
- ✗Confusing abstract class with interface: in C++ both are expressed the same way; an 'interface' is typically a class with only pure virtuals and no data
Follow-up questions
- →Can a pure virtual function have an implementation? When is that useful?
- →What is the difference between an abstract class and an interface in other languages?
JuniorTheoryVery commonWhat is the difference between private, protected, and public?
What is the difference between private, protected, and public?
public — accessible everywhere; protected — accessible inside the class and its derived classes; private — only inside the class and friends. Default in class is private, in struct is public.
Common mistakes
- ✗Thinking
protectedmeans 'accessible from outside the hierarchy' — it does not; only derived classes see it - ✗Forgetting that
friendbypasses all access specifiers for the specific class/function - ✗Using
protecteddata members in base classes — preferprivate+ protected getters to maintain encapsulation
Follow-up questions
- →How does access specifier on inheritance (
class D : private Base) change visibility of Base members in D? - →Why are
privatedata members preferred overprotectedin base classes?
JuniorTheoryVery commonWhat are const member functions? What can they call?
What are const member functions? What can they call?
A const member is declared with const after the parameter list; inside it, this is a pointer to const and the function cannot modify non-mutable members. Only const methods may be called on a const object.
Common mistakes
- ✗Calling a non-const member function on a const object — compile error
- ✗Returning a non-const reference/pointer to an internal member from a const function — violates logical constness
- ✗Not knowing that
mutableallows modifying a member even in a const method (e.g., for a cached value)
Follow-up questions
- →What is the difference between bitwise constness and logical constness?
- →How do you avoid code duplication when providing both const and non-const overloads?
JuniorTheoryVery commonWhat is a constructor? What types of constructors exist in C++?
What is a constructor? What types of constructors exist in C++?
A constructor initialises a new object. Kinds: default, copy (const T&), move (T&&), converting (one non-explicit arg), delegating, inherited (using Base::Base).
Common mistakes
- ✗Thinking the default constructor is always generated — it is suppressed if you declare any other constructor
- ✗Confusing a converting constructor with explicit conversion —
explicitprevents implicit conversions - ✗Not knowing that constructors do not have return types and cannot be virtual
Follow-up questions
- →When is the move constructor generated vs suppressed by the compiler?
- →What is the difference between a delegating constructor and a base-class constructor call in the initialiser list?
JuniorTheoryVery commonWhat is encapsulation? How is it implemented in C++?
What is encapsulation? How is it implemented in C++?
Encapsulation bundles data and operations into one unit and hides implementation details; in C++ this uses access specifiers — private data is exposed only via public/protected methods.
Common mistakes
- ✗Making all data
publicand calling that 'encapsulation' — data should be private with controlled access - ✗Providing raw getters and setters for every field — this is better than public data but still exposes implementation
- ✗Confusing encapsulation with information hiding — encapsulation is the mechanism; information hiding is the principle
Follow-up questions
- →What is the difference between encapsulation and abstraction?
- →When would you expose a reference to an internal container (e.g.,
begin()/end())?
JuniorTheoryVery commonWhat is inheritance and what types exist in C++?
What is inheritance and what types exist in C++?
Inheritance models 'is-a' and reuses base-class behaviour: public (is-a), protected (restricted is-a), private (implemented-in-terms-of). Multiple inheritance combines several bases; virtual inheritance avoids duplicating shared ancestors.
Common mistakes
- ✗Overusing inheritance where composition would be more appropriate (is-a vs has-a)
- ✗Not knowing the difference between public and private inheritance — private inheritance is 'implemented-in-terms-of', not 'is-a'
- ✗Deep inheritance hierarchies (more than 2-3 levels) become hard to maintain
Follow-up questions
- →What is the Liskov Substitution Principle and how does it constrain inheritance design?
- →When is composition clearly preferable to inheritance?
JuniorTheoryVery commonWhat is polymorphism?
What is polymorphism?
Polymorphism uses a single interface for different underlying types. C++ has three kinds: subtype (runtime, virtual functions), parametric (compile-time, templates), and ad-hoc (overloading).
Common mistakes
- ✗Expecting virtual dispatch on an object (by value) — object slicing strips the derived part
- ✗Confusing ad-hoc overloading with subtype polymorphism
- ✗Not knowing that templates provide parametric polymorphism without runtime cost
Follow-up questions
- →What is the cost of runtime polymorphism (vtable lookup, cache misses)?
- →When would you choose static polymorphism (CRTP/templates) over dynamic (virtual functions)?
JuniorTheoryVery commonWhat is the Rule of Three and how was it superseded by Rule of Five?
What is the Rule of Three and how was it superseded by Rule of Five?
Rule of Three: if you write any of (destructor, copy ctor, copy assign) you almost certainly need all three. C++11 added move ctor/assign → Rule of Five. Rule of Zero: use RAII members so no special members are needed.
Common mistakes
- ✗Defining only the destructor and inheriting buggy compiler-generated copy
- ✗Copy ctor copying pointer fields by value (shallow copy) — double delete
- ✗Mixing Rule of Three (no move) with C++11 vocabulary types that expect move
Follow-up questions
- →What does the compiler default for the copy ctor when you don't write one?
- →Why is Rule of Zero often easier to maintain?
JuniorTheoryVery commonWhy do base classes need a virtual destructor?
Why do base classes need a virtual destructor?
Deleting a derived object through a base pointer with a non-virtual destructor calls only the base destructor — UB. A virtual destructor ensures the derived destructor runs first, then each base. Polymorphic bases must have one.
Common mistakes
- ✗Not making the destructor virtual in a class designed to be inherited — common source of resource leaks
- ✗Adding virtual destructor to all classes 'just in case' — adds vtable overhead; only needed for polymorphic bases
- ✗Not knowing that
= defaultor= 0can be used on a virtual destructor
Follow-up questions
- →What is the guideline: if any virtual function exists, declare a virtual destructor?
- →Is a pure virtual destructor allowed? What must you provide alongside it?
JuniorTheoryVery commonWhat does the virtual keyword do?
What does the virtual keyword do?
virtual enables dynamic dispatch: the correct override is selected at runtime by the object's actual type, not the static type of the pointer/reference. Only the base declaration needs virtual; derived overrides inherit virtuality.
Common mistakes
- ✗Writing
virtualon every override in derived classes — harmless but redundant; useoverrideinstead - ✗Calling a virtual function on an object by value — no dynamic dispatch; the declared type is used
- ✗Forgetting that non-virtual functions cannot be correctly overridden — only hidden
Follow-up questions
- →What is the performance cost of a virtual call compared to a direct call?
- →Can static and virtual be combined on the same function? Why not?
MiddleTheoryVery commonWhat is the difference between a copy constructor and a copy-assignment operator?
What is the difference between a copy constructor and a copy-assignment operator?
Copy constructor creates a new object from an existing one (T b = a;); copy-assignment replaces the state of an already-constructed object (b = a;) and must handle self-assignment, typically via copy-and-swap.
Common mistakes
- ✗Forgetting self-assignment check in operator= —
delete[] data; data = new ...(other.data)is UB whenthis == &other - ✗Not defining both or neither — if one is user-defined, the other should be too (Rule of Five)
- ✗Returning
voidfrom operator= — it must returnT&to support chaining (a = b = c)
Follow-up questions
- →Explain the copy-and-swap idiom and why it provides strong exception safety.
- →When does the compiler generate implicit copy constructor and copy-assignment operator?
MiddleTheoryVery commonWhat is the difference between overloading and overriding?
What is the difference between overloading and overriding?
Overloading: multiple same-named functions with different parameter types, resolved at compile time; overriding: a derived class replaces a virtual function with the same signature, resolved at runtime via the vtable.
Common mistakes
- ✗Declaring a function with the same name but different signature in a derived class — this hides the base overloads, not overrides them (use
using Base::foo;to restore) - ✗Changing only the return type in a derived class and expecting it to be an override — return type must be covariant or identical
- ✗Thinking
staticmember functions can be overridden — they cannot; they are resolved at compile time
Follow-up questions
- →What is the hiding rule and how does
usingfix it? - →What does covariant return type mean in the context of overriding?
JuniorTheoryCommonWhat is the construction and destruction order in a class hierarchy?
What is the construction and destruction order in a class hierarchy?
Construction: virtual bases, then non-virtual bases (left to right), then data members (in declaration order), then the constructor body. Destruction is the exact reverse. Members are initialised in declaration order, not initialiser-list order.
Common mistakes
- ✗Writing the initialiser list in a different order from the declaration order and assuming they are initialised in list order — they are not
- ✗Calling virtual functions in the base constructor and expecting the derived override — the vptr points to the base at that point
- ✗Forgetting that static local variables in constructors are not destroyed in the same way as members
Follow-up questions
- →What happens if an exception is thrown partway through construction?
- →What is the order of destruction for class members vs base class?
JuniorTheoryCommonWhat is deep copying? When do you need it?
What is deep copying? When do you need it?
Shallow copy duplicates pointer values — both objects share one allocation, leading to double-free; deep copy allocates new memory and duplicates the pointed-to data, required whenever a class owns heap resources.
Common mistakes
- ✗Providing only a deep copy constructor without a deep copy-assignment operator — violates the Rule of Three/Five
- ✗Not using copy-and-swap idiom for assignment — it's self-assignment safe and exception-safe
- ✗Deep-copying when move semantics would suffice — unnecessary allocations
Follow-up questions
- →What is the copy-and-swap idiom and why is it exception-safe?
- →How do move semantics reduce the need for deep copies?
JuniorTheoryCommonWhat are the ways to initialize class fields in C++?
What are the ways to initialize class fields in C++?
Three ways: member initialiser list in the constructor (only way for const/reference members and bases); default member initialisers (C++11) in the class body; and assignment in the constructor body (default-construct then assign).
Common mistakes
- ✗Assigning in the constructor body instead of using the initialiser list for non-trivial types — causes extra construction
- ✗Not knowing the initialisation order follows declaration order, not initialiser list order
- ✗Forgetting that references and const members can only be initialised via the initialiser list, not assigned in the body
Follow-up questions
- →When do default member initialisers conflict with initialiser list entries?
- →What is aggregate initialisation and when can you use it without a constructor?
JuniorTheoryCommonHow do you protect an object from being copied?
How do you protect an object from being copied?
Declare the copy constructor and copy-assignment operator as = delete; any copy attempt becomes a compile error. For movable-but-not-copyable types only the copy operations are deleted while move operations are defaulted.
Common mistakes
- ✗Using the old C++03 technique of making copy operations private — prefer
= deletein C++11 and later - ✗Forgetting to delete the copy-assignment operator when deleting the copy constructor
- ✗Not knowing that deleting copy operations does not automatically delete move operations
Follow-up questions
- →What is the difference between deleted and private copy operations in terms of error messages?
- →How does
boost::noncopyableimplement the same thing and why is it sometimes preferred?
JuniorTheoryCommonCan a pure virtual function have an implementation? What happens if called from a constructor?
Can a pure virtual function have an implementation? What happens if called from a constructor?
Yes, a pure virtual function can have an out-of-line body, callable explicitly via Base::method(). Calling it through virtual dispatch from a constructor/destructor is UB — at that point the vtable points to the base.
Common mistakes
- ✗Assuming pure virtual means 'no implementation allowed' — it means 'derived must override', not 'no body can exist'
- ✗Calling virtual functions (pure or not) from a constructor expecting derived-class dispatch — it won't dispatch to the derived override
- ✗Not making the pure virtual destructor explicitly defined — linker requires it
Follow-up questions
- →Why does calling a virtual function in a constructor use the base class's version?
- →What is the practical use of providing a body for a pure virtual function?
JuniorTheoryCommonWhat is a static member function or field? How do they differ from non-static?
What is a static member function or field? How do they differ from non-static?
A static data member belongs to the class itself and is shared by all instances; a static member function has no this and can only access other static members. Static members exist for the whole program lifetime.
Common mistakes
- ✗Defining a static data member both in the header declaration and in a .cpp — results in linker errors or duplicate symbols
- ✗Calling a virtual function through a static member function — not possible without an object
- ✗Confusing static class member with a static local variable inside a function — completely different semantics
Follow-up questions
- →Where must a non-inline static data member be defined?
- →When is it appropriate to use a static member function vs a free function in an anonymous namespace?
JuniorTheoryCommonWhat is virtual inheritance? How does it solve the diamond problem?
What is virtual inheritance? How does it solve the diamond problem?
The diamond problem: two bases share a common ancestor and the derived class inherits two copies. Virtual inheritance ensures one shared sub-object; the most-derived class constructs the virtual base directly, adding a vbptr per object.
Common mistakes
- ✗Forgetting that the most-derived class must explicitly call the virtual base constructor — intermediate classes' calls are ignored
- ✗Not realising virtual inheritance adds overhead (vbptr, indirect member access)
- ✗Using virtual inheritance unnecessarily — prefer composition over deep inheritance hierarchies
Follow-up questions
- →What is the vbptr (virtual base pointer) and when is it created?
- →How can you solve the diamond problem without virtual inheritance?
MiddleDebuggingCommonWhy does w = w; break this operator=?
Why does w = w; break this operator=?
On self-assignment (w = w;) it deletes data_, then dereferences the now-freed other.data_ (the same pointer) — use-after-free, UB. Fix: guard with if (this != &other), or use the copy-and-swap idiom, which is self-assignment-safe and exception-safe.
Common mistakes
- ✗Assuming self-assignment never happens in real code
- ✗Thinking the problem is a missing return statement
- ✗Believing it leaks rather than uses freed memory
Follow-up questions
- →Why is the copy-and-swap idiom both self-assignment-safe and exception-safe?
- →How does copy-and-swap reuse the copy constructor and the destructor?
MiddleTheoryCommonWhat are = default and = delete?
What are = default and = delete?
= default asks the compiler to generate the default implementation of a special member; = delete removes a function from the overload set so calling it is a compile-time error.
Common mistakes
- ✗Confusing
= defaultwith an empty body{}— they differ:{}user-defines the function (suppressing other specials),= defaultis a compiler-provided definition - ✗Placing
= deleteon a function that is never called — it only matters if the function could be selected by overload resolution - ✗Not knowing that
= deletecan be applied to any function, not just special members — useful to block specific overloads
Follow-up questions
- →What happens to the move constructor if you declare a copy constructor as
= default? - →How do you use
= deleteto prevent a function from being called with a specific argument type?
MiddleTheoryCommonWhat is a delegating constructor?
What is a delegating constructor?
A delegating constructor (C++11) calls another constructor of the same class in its initialiser list, avoiding code duplication. Only the delegated-to constructor may have member initialisers; the delegating body runs after delegation completes.
Common mistakes
- ✗Creating a delegation cycle — the compiler detects this, but it is still a confusing error
- ✗Mixing delegation with member initialisers in the same initialiser list — not allowed; a delegating constructor cannot have other member initialisers
- ✗Not knowing that the delegated-to constructor fully constructs the object before the delegating body runs
Follow-up questions
- →What is the difference between a delegating constructor and a
privateinit helper function? - →How does delegation interact with exception handling in constructors?
MiddleTheoryCommonWhat is an explicit constructor and why does it matter?
What is an explicit constructor and why does it matter?
A single-argument constructor is a converting constructor and allows implicit conversions; marking it explicit forbids the compiler from using it implicitly, requiring direct initialisation.
Common mistakes
- ✗Forgetting
expliciton constructors taking a single argument — leads to hard-to-spot implicit conversions - ✗Not knowing that
explicitalso applies to conversion operators (explicit operator bool()) - ✗Using
expliciton constructors taking two or more arguments — has no effect before C++11; from C++11 it affects brace-init
Follow-up questions
- →What is
explicit operator bool()used for? Give an example. - →How does C++20
explicit(condition)extend the functionality?
MiddleTheoryCommonWhat is friend and when should you use it?
What is friend and when should you use it?
friend grants another class or function full access to private and protected members; friendship is not inherited, not transitive, and not mutual, and is typically used for non-member operator overloads.
Common mistakes
- ✗Declaring
friendfor every class that needs access — sign of a design smell; prefer getters/setters or restructuring - ✗Thinking friendship is inherited — a derived class does not automatically get friend access
- ✗Forgetting to forward-declare a class before using it in a friend declaration in some contexts
Follow-up questions
- →How do you implement
operator<<for a class with private data as a friend function? - →Is there a way to give only partial access to private members without
friend?
MiddleTheoryCommonWhat is a member initialiser list and why prefer it over constructor body assignment?
What is a member initialiser list and why prefer it over constructor body assignment?
The initialiser list constructs members directly; body assignment first default-constructs then assigns — two operations. It is mandatory for const, reference, and non-default-constructible members.
Common mistakes
- ✗Writing the initialiser list in a different order from the member declaration order — initialisation happens in declaration order regardless
- ✗Trying to initialise a const member in the constructor body — not allowed
- ✗Initialising a member using another member that hasn't been initialised yet (ordering issue)
Follow-up questions
- →When would you prefer in-class default member initialisers (C++11) over the initialiser list?
- →What is the performance difference between initialiser list and body assignment for
std::string?
MiddleTheoryCommonWhat is the difference between an interface and an abstract class in C++?
What is the difference between an interface and an abstract class in C++?
C++ has no interface keyword — an interface is a convention: a class with only pure virtual functions and no data; an abstract class has at least one pure virtual but may also have data and concrete methods.
Common mistakes
- ✗Omitting the virtual destructor in an interface class — deleting a derived object through an interface pointer is UB
- ✗Adding data members to an interface — breaks the pure-contract idiom and can cause diamond-inheritance layout issues
- ✗Marking interface methods
final— prevents overriding and defeats the purpose
Follow-up questions
- →How do you simulate Java-style interfaces with multiple inheritance in C++?
- →What is the cost of adding multiple pure-virtual interfaces to a class?
MiddleTheoryCommonWhat are the pros, cons, and construction order of multiple inheritance with virtual bases?
What are the pros, cons, and construction order of multiple inheritance with virtual bases?
Multiple inheritance combines bases but causes the diamond problem (ambiguity, duplicate sub-objects); virtual inheritance shares one sub-object, and the most-derived class must call the virtual base constructor.
Common mistakes
- ✗Forgetting that in virtual inheritance the most-derived class must explicitly call the virtual base constructor even through deep hierarchies
- ✗Mixing virtual and non-virtual inheritance of the same base — results in both a shared virtual sub-object and a separate non-virtual copy
- ✗Accessing an ambiguous member without qualification — should use
Base::memberto disambiguate
Follow-up questions
- →What is the construction order in
class D : virtual B1, virtual B2? - →How does virtual inheritance affect the size and layout of objects?
MiddleTheoryCommonWhy use override? What does final do on a virtual function?
Why use override? What does final do on a virtual function?
override tells the compiler the function must override a base virtual; if none matches, compilation fails — catching silent signature-mismatch bugs. final forbids further overriding or derivation.
Common mistakes
- ✗Omitting
override— a typo in the signature creates a new non-virtual function instead of overriding, silently breaking polymorphism - ✗Thinking
finalon a function impliesoverride— they are independent; you may need both:void foo() override final - ✗Using
finalto optimise dispatch — while some compilers devirtualizefinalmethods, it is not guaranteed and should not be the primary motivation
Follow-up questions
- →Can a non-virtual function be marked
override? What error do you get? - →How does
finalhelp the compiler devirtualize calls?
MiddleTheoryCommonWhat types of polymorphism exist in C++?
What types of polymorphism exist in C++?
Three main kinds: subtype (runtime) via virtual functions and vtable; parametric (compile-time) via templates; ad-hoc via function/operator overloading. CRTP provides static subtype-like dispatch.
Common mistakes
- ✗Calling virtual function polymorphism the only kind — templates and overloading are equally important
- ✗Forgetting that static polymorphism (CRTP) requires knowing all subtypes at compile time — not extensible at runtime
- ✗Conflating polymorphism with inheritance — they are related but distinct concepts
Follow-up questions
- →In what situations would you choose CRTP over virtual functions?
- →How does
std::variantwithstd::visitrelate to polymorphism?
MiddleDesignCommonYou are reviewing a class that owns a resource and declares only a custom destructor. Explain the resource-management guideline Rule of Five — which special members it covers, when a class genuinely needs all five, and when you can avoid writing any of them.
You are reviewing a class that owns a resource and declares only a custom destructor. Explain the resource-management guideline Rule of Five — which special members it covers, when a class genuinely needs all five, and when you can avoid writing any of them.
If a class needs a custom destructor, copy ctor, or copy assignment, it almost certainly needs all five: destructor, copy ctor, copy assignment, move ctor, move assignment. Use RAII members (unique_ptr, vector) to avoid writing them.
Common mistakes
- ✗Defining only a destructor and relying on the compiler-generated copy — leads to double-free when the resource is raw
- ✗Forgetting the move operations; the compiler may not generate them when a destructor or copy is defined (Rule of Zero is better when possible)
- ✗Writing a move constructor that copies instead of moves the resource
Follow-up questions
- →What is the Rule of Zero?
- →How does =default differ from not declaring a special member function at all?
MiddleDebuggingCommonWhat is object slicing and how do you prevent it?
What is object slicing and how do you prevent it?
Slicing occurs when a derived object is assigned or copied into a base by value, discarding all derived members. Prevent it via references, pointers, or smart pointers — or by deleting the base copy constructor.
Open full question →Common mistakes
- ✗Storing polymorphic objects in std::vector<Base> by value — each element is sliced on insertion
- ✗Passing a derived object to a function that accepts Base by value
- ✗Assigning a derived to a base variable without realising it compiles silently
Follow-up questions
- →Why does std::vector<Base> cause slicing?
- →How does deleting the copy constructor in an abstract base class prevent slicing?
MiddleTheoryCommonWhat is this, and what happens when a static member is accessed through a null pointer?
What is this, and what happens when a static member is accessed through a null pointer?
Non-static methods receive a hidden this pointer; static members have no this, so ptr->staticMethod() may compile with null ptr (use Type::method() instead). Calling a non-static method on null is UB.
Common mistakes
- ✗Assuming a member call is safe just because the method does not visibly use fields
- ✗Calling static members through an object pointer instead of the type name
- ✗Forgetting that this is not available inside static member functions
Follow-up questions
- →How is a non-static member function call lowered by the compiler?
- →Can this ever be null inside a valid non-static member function call?
MiddleTheoryCommonHow does virtual dispatch work and what is the vtable?
How does virtual dispatch work and what is the vtable?
Each polymorphic class has a static vtable (array of function pointers); each instance carries a hidden vptr. A virtual call dereferences the vptr, indexes the vtable, and jumps — the most-derived override wins.
Common mistakes
- ✗Calling virtual functions in a constructor or destructor — the object's type is not yet fully established, so the base-class version is called, not the most-derived
- ✗Expecting virtual dispatch through a value (object slicing strips the derived part)
- ✗Forgetting that virtual dispatch requires at least one level of indirection and cannot be inlined by default
Follow-up questions
- →What is the cost of a virtual call on modern CPUs?
- →How can you achieve polymorphism without virtual functions (type erasure, CRTP, std::variant)?
JuniorTheoryOccasionalHow much memory does an empty class class A {}; occupy?
How much memory does an empty class class A {}; occupy?
sizeof(A) is 1, not 0 — the standard requires every distinct object to have a unique address. As a base, the Empty Base Optimisation (EBO) allows an empty base sub-object to take zero size.
Common mistakes
- ✗Assuming
sizeof(EmptyBase)== 0 — it is 1 for standalone objects - ✗Not knowing about EBO — compilers apply it when a base class is empty, which is essential for zero-overhead policy classes
- ✗Confusing an empty class with a class containing only static members — static members don't affect instance size
Follow-up questions
- →What is
[[no_unique_address]](C++20) and how does it relate to EBO? - →Why is sizeof always at least 1 for any type in C++?
JuniorTheoryOccasionalHow do you prevent a class from being inherited? What does final do?
How do you prevent a class from being inherited? What does final do?
Use final on the class (class C final {};) to forbid further inheritance and let the compiler devirtualise calls. You can also apply final to individual virtual functions to forbid overriding them in subclasses.
Common mistakes
- ✗Not knowing the difference between
finalon a class andfinalon a virtual function - ✗Using
privateinheritance to prevent further subclassing — it doesn't prevent it;finaldoes - ✗Forgetting that
overrideandfinalare context-sensitive identifiers, not reserved keywords — they can be used as variable names (though not recommended)
Follow-up questions
- →How can the compiler optimise calls to
finalvirtual functions? - →What is devirtualisation and when does it apply?
JuniorTheoryOccasionalWhat does mutable do and when should you use it?
What does mutable do and when should you use it?
mutable allows a member to be modified through a const object or inside a const method; typical uses are lazy caches and mutexes that need locking in read-only operations.
Common mistakes
- ✗Using
mutablefor members that actually change the observable state — this defeats the purpose ofconst - ✗Forgetting that
mutabledoes not affectconstreferences or pointers — only non-static data members - ✗Declaring a mutex as non-mutable — then you cannot lock it inside a
constmethod
Follow-up questions
- →How do you implement a thread-safe lazy cache with
mutable? - →Can
mutablebe applied to a static member? Why not?
MiddleTheoryOccasionalWhat is the field initialisation order? What happens if the initialiser list is out of order?
What is the field initialisation order? What happens if the initialiser list is out of order?
Fields are initialised in declaration order, not initialiser-list order. If the list references a field declared later, it is read uninitialised — undefined behaviour. Compilers warn with -Wreorder.
Common mistakes
- ✗Writing
x(y + 1), y(0)when y is declared after x —yis uninitialised whenxis constructed - ✗Not knowing that base classes are always fully constructed before any derived member
- ✗Ignoring
-Wreorderwarnings from the compiler
Follow-up questions
- →How would you enforce correct initialisation order when one member depends on another?
- →What is the pitfall of initialising a member with
this->otherMemberin the initialiser list?
MiddleTheoryOccasionalHow does the compiler distinguish member variables from local variables with the same name?
How does the compiler distinguish member variables from local variables with the same name?
Lookup searches scopes from innermost to outermost, so locals shadow members; use this->name or ClassName::name to disambiguate, and conventions like m_name/name_ avoid the issue entirely.
Common mistakes
- ✗Writing
x = x;in a constructor body when both a parameter and member are namedx— only the local parameter is visible, the assignment is a no-op - ✗Forgetting that
this->is needed inside lambdas to capture members by name (pre-C++20) - ✗Thinking
ClassName::memberworks inside a non-static method withoutthis— it does for static members only
Follow-up questions
- →What is argument-dependent lookup (ADL) and how does it differ from member lookup?
- →How does name lookup differ inside a template class body vs an ordinary class body?
MiddleDebuggingOccasionalWhy might deleting through this Base* leak?
Why might deleting through this Base* leak?
Deleting a Derived through a Base* whose destructor is not virtual is undefined behavior — in practice ~Derived never runs, leaking the resources it owns. Fix: give a polymorphic base a virtual destructor (or own it via unique_ptr).
Common mistakes
- ✗Assuming the destructor dispatches virtually without being declared virtual
- ✗Believing the base destructor is enough because the pointer's static type is Base
- ✗Confusing this with object slicing — here the object is intact, only its cleanup is wrong
Follow-up questions
- →When does a base class NOT need a virtual destructor?
- →How does owning the object via
std::unique_ptr<Base>avoid this bug?
MiddleTheoryOccasionalWhat is private inheritance used for?
What is private inheritance used for?
Private inheritance expresses 'implemented-in-terms-of', not 'is-a': base public/protected members become private in the derived class. Use it for overriding virtuals, EBO, or accessing protected base members; otherwise prefer composition.
Common mistakes
- ✗Confusing private inheritance with composition — private inheritance still creates a base sub-object and can override virtuals; composition cannot
- ✗Using
using Base::member;inside the derived class to restore access — this is valid and sometimes intentional - ✗Thinking private inheritance prevents the
is-acheck —static_castto the base still works inside the class
Follow-up questions
- →What is the Empty Base Optimisation and why does it only apply to (private) inheritance, not composition?
- →When does
using Base::foo;inside a privately-derived class make sense?
MiddleDesignOccasionalYou are designing a base class and considering exposing some of its internal state to subclasses via protected rather than private. Explain when protected members are the right choice and what downsides they bring to encapsulation and future maintenance.
You are designing a base class and considering exposing some of its internal state to subclasses via protected rather than private. Explain when protected members are the right choice and what downsides they bring to encapsulation and future maintenance.
protected exposes a member only to derived classes — broader than private, narrower than public. Use sparingly: it loosens encapsulation toward subclasses you don't control.
Common mistakes
- ✗Making all members
protected'just in case' — leaks internals to everyone - ✗Confusing
protectedaccess withprotectedinheritance — different mechanisms - ✗Treating
protectedas a synonym for 'package-private' (it isn't in C++)
Follow-up questions
- →What is the Non-Virtual Interface (NVI) idiom?
- →How does
protectedinteract withfriend?
MiddleTheoryOccasionalWhat is static polymorphism in C++ and how does it compare to dynamic polymorphism?
What is static polymorphism in C++ and how does it compare to dynamic polymorphism?
Static polymorphism resolves at compile time (templates, overloading, CRTP) with no runtime cost; dynamic polymorphism (virtual functions) resolves at runtime via vtable, supporting heterogeneous containers and runtime types.
Common mistakes
- ✗Choosing virtual when the type is known at compile time — pays for nothing
- ✗Choosing CRTP when one or two types exist — overengineering for two cases
- ✗Mixing both styles in one hierarchy without clear separation
Follow-up questions
- →How does CRTP achieve compile-time polymorphism?
- →When does C++23 deducing-this replace CRTP?
SeniorTheoryOccasionalWhat is the Empty Base Optimization and when does it apply?
What is the Empty Base Optimization and when does it apply?
The Empty Base Optimization (EBO) lets a base class with no non-static data members occupy zero bytes inside a derived object, instead of the minimum 1 byte a standalone empty class needs. It applies only when the empty base is inherited, not held as a member.
Common mistakes
- ✗Storing a stateless functor as a member instead of a base, paying an avoidable byte plus padding
- ✗Assuming EBO is guaranteed — it is permitted but compilers may decline in edge cases
- ✗Forgetting that two empty bases of the same type still need distinct addresses, blocking EBO
Follow-up questions
- →How does
[[no_unique_address]]achieve the same effect for data members? - →Why does
std::tuplerely on EBO for its empty element types?
SeniorDebuggingOccasionalWhy can member initialization order cause subtle bugs?
Why can member initialization order cause subtle bugs?
Non-static data members are initialized in declaration order, never in the order they appear in the member initializer list. If one member's initializer reads another member declared later, that member is still uninitialized garbage. Enable -Wreorder to catch this.
Common mistakes
- ✗Assuming initializer-list order drives initialization rather than declaration order
- ✗Initializing one member from another declared later, reading uninitialized garbage
- ✗Ignoring or disabling the
-Wreorderwarning instead of fixing the real ordering
Follow-up questions
- →Does the same ordering rule apply to base-class subobjects relative to members?
- →How can you restructure a class to remove an inter-member initialization dependency?
SeniorTheoryOccasionalHow is a class with multiple inheritance and virtual functions laid out in memory?
How is a class with multiple inheritance and virtual functions laid out in memory?
Each polymorphic base contributes its own vptr; sub-objects sit consecutively, casting to a secondary base adjusts the pointer by an offset, and virtual inheritance adds a vbptr for the shared sub-object.
Common mistakes
- ✗Assuming
reinterpret_castworks correctly between base and derived with multiple inheritance — it ignores the required offset adjustment, unlikestatic_cast - ✗Thinking
sizeof(Derived)equals the sum of base sizes plus derived members — padding and extra pointers can increase it significantly - ✗Forgetting that with virtual inheritance the most-derived object ends up with just one shared virtual base sub-object, reducing size compared to non-virtual diamond
Follow-up questions
- →How does
dynamic_castnavigate the vtable to find the correct base sub-object? - →Show the approximate in-memory layout of
class D : public B1, public B2where both bases have virtual functions.
SeniorTheoryOccasionalWhat is the Non-Virtual Interface (NVI) idiom and why use it?
What is the Non-Virtual Interface (NVI) idiom and why use it?
The Non-Virtual Interface idiom makes the public interface non-virtual and the customization points private virtual. The public method runs shared pre/post logic and delegates the variable part to a private hook, so subclasses cannot skip the wrapper code.
Common mistakes
- ✗Making the customization hooks public, which defeats the wrapper-enforcement guarantee
- ✗Confusing NVI with the Template Method pattern — NVI is the C++ access-control mechanism for it
- ✗Assuming a private virtual cannot be overridden — derived classes can still override it
Follow-up questions
- →How can a derived class override a private
virtualit cannot even call? - →How does NVI make it easier to add cross-cutting concerns like logging later?
SeniorTheoryOccasionalCan a pure virtual function have a definition, and when is that useful?
Can a pure virtual function have a definition, and when is that useful?
Yes — = 0 only marks the class abstract and forces derived classes to override; it does not forbid a definition, which must be out of line. It is useful as a shared default invoked via Base::method(), and is mandatory for a pure virtual destructor.
Common mistakes
- ✗Trying to define the pure virtual body inline at the declaration — it must be out of line
- ✗Forgetting to define a pure virtual destructor, causing a linker error
- ✗Believing a defined pure virtual makes the class concrete — it stays abstract
Follow-up questions
- →Why must a pure virtual destructor always have a definition?
- →How does a derived override call the base's pure virtual default?
SeniorTheoryOccasionalWhich special members does declaring a destructor or a copy operation suppress?
Which special members does declaring a destructor or a copy operation suppress?
Declaring a destructor, copy constructor, or copy assignment suppresses implicit generation of the move constructor and move assignment, so the class silently copies instead of moving. Declaring either copy operation also deprecates the other one.
Common mistakes
- ✗Adding a
~Base() = default;destructor and not noticing moves silently became copies - ✗Believing copy and move generation are independent — declaring one copy op affects the other
- ✗Not applying the Rule of Five when any one special member is user-declared
Follow-up questions
- →Why is silently copying instead of moving a performance problem rather than a correctness one?
- →How does
= defaultdiffer from leaving a special member undeclared?
SeniorTheoryOccasionalWhat makes a class "standard-layout" and why does it matter?
What makes a class "standard-layout" and why does it matter?
A standard-layout class has no virtual functions or virtual bases, all non-static data members with the same access control, and members declared in at most one class. It has a predictable, compiler-independent layout and is fully C-struct compatible.
Common mistakes
- ✗Confusing standard-layout with trivial — they are independent properties, a type can be one without the other
- ✗Mixing
publicandprivatenon-static data members and expectingoffsetofto stay defined - ✗Adding a virtual function to a C-interop struct and breaking its ABI compatibility
Follow-up questions
- →How does standard-layout differ from a trivial type and from an aggregate?
- →What is the common initial sequence and when is it safe to rely on?
SeniorDebuggingOccasionalWhy doesn't a virtual call inside a constructor dispatch to the derived override?
Why doesn't a virtual call inside a constructor dispatch to the derived override?
During each constructor stage the object's vptr points to that class's own vtable, because more-derived parts do not exist yet. A virtual call from the base constructor resolves to the base version. Calling a pure virtual this way is undefined behavior.
Common mistakes
- ✗Calling virtual hooks from a base constructor expecting derived behavior to run
- ✗Calling a pure virtual from a constructor or destructor — undefined behavior, not just the base version
- ✗Trying to fix it with
dynamic_cast<Derived*>(this)inside the base constructor
Follow-up questions
- →Does the same rule apply to virtual calls inside a destructor, and why?
- →How does a two-phase init or a factory function solve this cleanly?
SeniorCodeOccasionalSketch the vtable layout and vptr setup for a hierarchy
Sketch the vtable layout and vptr setup for a hierarchy
Each polymorphic class has a static vtable; each instance carries a hidden vptr. Each constructor stage resets the vptr to its class's vtable — so virtual calls in constructors don't reach derived overrides.
Common mistakes
- ✗Thinking the vtable is per-instance — it is per-class (static data)
- ✗Assuming virtual inheritance does not introduce additional vptr complexity
- ✗Using reinterpret_cast to read the vptr directly — non-portable ABI hack
Follow-up questions
- →What is a pure virtual function and how does it appear in the vtable?
- →How does multiple inheritance affect vtable layout?
JuniorTheoryRareWhat is the difference between class, struct, and union in C++?
What is the difference between class, struct, and union in C++?
class and struct differ only by defaults: class is private by default, struct is public. union overlays all members in one memory location — only one member is active at a time.
Common mistakes
- ✗Thinking struct is a C-only type and cannot have methods, constructors, or inheritance
- ✗Reading an inactive union member without managing object lifetime
- ✗Using union where std::variant would express ownership and active state safely
Follow-up questions
- →How do default access rules affect inheritance?
- →When would you still use union in modern C++?
JuniorCodeRareWrite a Logger class that logs to console or file
Write a Logger class that logs to console or file
Logger wraps a std::ostream& and writes timestamped, level-prefixed lines. Accepting the stream by reference lets callers pass std::cout, an std::ofstream, or any ostream. The class must be non-copyable since streams are not copyable.
Common mistakes
- ✗Storing the stream by value instead of reference — streams are not copyable
- ✗Not flushing after each log line — the last messages may be lost on crash
- ✗Ignoring thread safety — two threads writing simultaneously produce interleaved output; use a
std::mutex
Follow-up questions
- →How would you add log levels so that
DEBUGmessages are filtered out in production? - →How do you make Logger thread-safe with minimal lock contention?
JuniorTheoryRareWhat are the main OOP principles?
What are the main OOP principles?
The four OOP principles: encapsulation (data + methods, controlled access), abstraction (expose only needed), inheritance (IS-A reuse), and polymorphism (uniform interface, different behaviour).
Common mistakes
- ✗Treating inheritance as the primary reuse mechanism — prefer composition; inheritance is for polymorphism, not code reuse
- ✗Confusing encapsulation with data hiding — encapsulation is about controlling access; you can have public data (aggregate struct) and still be 'encapsulated' if the invariants are maintained externally
- ✗Implementing polymorphism only via virtual functions — templates and concepts provide zero-cost compile-time polymorphism which is often preferable in C++
Follow-up questions
- →How does C++ implement runtime polymorphism internally (vtable, vptr)?
- →What is the difference between OOP and data-oriented design (DOD) and when does DOD win?
JuniorTheoryRareWhat's the difference between a pure-virtual class, an abstract class, and an interface in C++?
What's the difference between a pure-virtual class, an abstract class, and an interface in C++?
C++ has no interface keyword — interface is a convention: only pure-virtual functions plus a virtual destructor. Abstract class: at least one pure-virtual but may have data and bodies.
Common mistakes
- ✗Forgetting the virtual destructor on an interface — leak when deleting through base
- ✗Adding data members to an 'interface' — defeats the purpose
- ✗Calling virtual member functions from constructors of abstract bases
Follow-up questions
- →Why does C++ use multiple inheritance instead of separate interface keyword?
- →How does NVI (Non-Virtual Interface) work alongside abstract classes?
MiddleCodeRareDesign a testable DB-backed product service
Design a testable DB-backed product service
Define IProductRepository (pure virtual fetchByFilter), SqlProductRepository for real queries, FakeProductRepository for tests. ProductService depends only on the interface — Dependency Injection enables tests without a database.
Common mistakes
- ✗Hardcoding the database call inside the service — impossible to unit test without a real DB
- ✗Not making the destructor of the repository interface virtual — deleting through base pointer is UB
- ✗Returning raw pointers from the repository — use
std::vector<Product>by value or smart pointers
Follow-up questions
- →How would you add pagination to
fetchByFilter? - →How does this design change if the repository must be thread-safe?
MiddleCodeRareImplement Conway's Game of Life in OOP style
Implement Conway's Game of Life in OOP style
Split into Grid (owns cells, computes next generation) and a Printer/Renderer (displays). Grid::step() counts live neighbours and applies the four rules using a double buffer — writing to a new grid and swapping — to avoid in-place mutation bugs.
Common mistakes
- ✗Updating cells in-place — a cell updated in the current step affects neighbour counts later in the same step
- ✗Hardcoding grid dimensions as globals instead of constructor parameters
- ✗Not handling boundary conditions (edge cells have fewer than 8 neighbours)
Follow-up questions
- →How would you support an infinite (wrap-around toroidal) grid?
- →How do you make
step()run in parallel with multiple threads?
MiddleCodeRareWhat does this member initializer list print?
What does this member initializer list print?
s.b is 5, but s.a is garbage. Members initialize in declaration order (a then b), not the order written in the initializer list, so a(b) runs first and reads b before it is set. Compile with -Wreorder to catch this.
Common mistakes
- ✗Believing the initializer-list order drives initialization
- ✗Expecting a compile error rather than a read of an uninitialized member
- ✗Not knowing
-Wreorderwarns about a list/declaration mismatch
Follow-up questions
- →Why does the standard fix initialization to declaration order?
- →What exactly does
-Wreorderwarn about?
MiddleCodeRareWrite a correct String class with the Rule of Five
Write a correct String class with the Rule of Five
String owns a heap char[] and must follow the Rule of Five: destructor, copy ctor, copy-assignment via copy-and-swap, move ctor, move-assignment. Move operations null the source pointer so the source destructor doesn't double-free.
Common mistakes
- ✗Forgetting the null terminator in size calculations —
new char[len_ + 1] - ✗Not handling self-assignment in operator= without copy-and-swap
- ✗Implementing move constructor without zeroing the source pointer — the source destructor then double-frees
Follow-up questions
- →How does the copy-and-swap idiom provide strong exception safety?
- →What changes when you add SSO (small string optimisation)?
SeniorTheoryRareIn what order are bases and members destroyed, and why must the destructor be virtual?
In what order are bases and members destroyed, and why must the destructor be virtual?
Destruction runs in reverse of construction: most-derived destructor body first, then members in reverse declaration order, then bases. The base destructor must be virtual when deleting through a base pointer — otherwise the derived part leaks (UB).
Common mistakes
- ✗Deleting a derived object through a base pointer whose class lacks a virtual destructor
- ✗Assuming destruction order matches declaration order rather than reversing it
- ✗Thinking a virtual destructor is unneeded if the derived class adds no data members — it is still UB
Follow-up questions
- →Why is omitting a virtual destructor UB even when the derived class has a trivial destructor?
- →When is a non-virtual protected destructor the right design choice instead?