1. What occurs if foo() is invoked inside B’s constructor during the instantiation of a derived class object D?
Answer: The base version B::foo() executes, not the derived override.
This is the “virtual calls don’t dispatch during construction” rule. The reason is initialization order. When you create a D, the base subobject B is constructed first. During B’s constructor, the object’s virtual table pointer (vptr) is set to B’s vtable — the derived class D hasn’t started constructing yet, and its members don’t exist.
So when B’s constructor calls the virtual foo(), the runtime looks up the vptr and finds B’s vtable. The call resolves to B::foo(). This is deliberate: calling D::foo() would execute code that may read D’s data members — which are not yet initialized (in fact, for data members they’d be uninitialized, and for the vptr of D they’re not even set). The language prevents this hazard by fixing the dynamic type to B for the duration of the base constructor.
The same rule applies in the destructor: during ~B, the vptr reverts to B’s vtable, and virtual calls resolve to the base version.
The interview answer: B::foo() runs, because the vptr points to the base vtable until the derived constructor begins — virtual dispatch during base construction resolves to the base.
Answer:
The base version B::foo() executes, not the derived override.
This is the “virtual calls don’t dispatch during construction” rule. The reason is initialization order. When you create a D, the base subobject B is constructed first. During B’s constructor, the object’s virtual table pointer (vptr) is set to B’s vtable — the derived class D hasn’t started constructing yet, and its members don’t exist.
So when B’s constructor calls the virtual foo(), the runtime looks up the vptr and finds B’s vtable. The call resolves to B::foo(). This is deliberate: calling D::foo() would execute code that may read D’s data members — which are not yet initialized (in fact, for data members they’d be uninitialized, and for the vptr of D they’re not even set). The language prevents this hazard by fixing the dynamic type to B for the duration of the base constructor.
The same rule applies in the destructor: during ~B, the vptr reverts to B’s vtable, and virtual calls resolve to the base version.
The interview answer: B::foo() runs, because the vptr points to the base vtable until the derived constructor begins — virtual dispatch during base construction resolves to the base.
2. What functionality does the mutable specifier provide when applied to a non-static class member variable?
Answer: It allows the member to be mutated inside const member functions.
The const on a member function makes the this pointer const-qualified: the function promises not to modify the object. A mutable member is exempt from that promise. Even in a const member function, a mutable member can be read and written.
Why would you want that? There are two classic cases:
- Logical vs. bitwise constness: an object is logically const (its externally observable data never changes), but it has internal bookkeeping that needs updating. The canonical examples are a mutex (locking it must not require a non-const object) and a memoization cache (lazily computing and storing a result is fine even on a const object).
- Reference counters or debugging statistics.
So mutable doesn’t add thread-safety and doesn’t change memory layout — it only relaxes the const restriction for one specific field. The interview answer: mutable permits modification of that member inside const member functions, used for things like mutexes and caches.
Answer:
It allows the member to be mutated inside const member functions.
The const on a member function makes the this pointer const-qualified: the function promises not to modify the object. A mutable member is exempt from that promise. Even in a const member function, a mutable member can be read and written.
Why would you want that? There are two classic cases:
- Logical vs. bitwise constness: an object is logically const (its externally observable data never changes), but it has internal bookkeeping that needs updating. The canonical examples are a mutex (locking it must not require a non-const object) and a memoization cache (lazily computing and storing a result is fine even on a const object).
- Reference counters or debugging statistics.
So mutable doesn’t add thread-safety and doesn’t change memory layout — it only relaxes the const restriction for one specific field. The interview answer: mutable permits modification of that member inside const member functions, used for things like mutexes and caches.
3. How does a C++ class containing virtual functions incur runtime memory and execution overhead?
Answer: Each instance carries a hidden virtual pointer (vptr) to a per-class virtual table (vtable), adding a pointer-sized memory cost and an indirection on each virtual call.
When a class declares at least one virtual function, the compiler adds a hidden pointer to each object: the vptr. All objects of the same class point to the same static vtable — an array of function pointers, one per virtual function, shared by the whole class.
Two costs:
- Memory: one pointer per instance (typically 8 bytes), even if the class has no data members.
- Dispatch: a virtual call is not a direct call. The runtime loads the vptr, indexes into the vtable, and calls through the function pointer — an extra indirection compared to a non-virtual call. In practice this is cheap, but it exists, and it also defeats some compiler inlining opportunities.
The trade-off buys runtime polymorphism: the same virtual call on a Base* can resolve to different derived implementations depending on the object’s actual type. The interview answer: a per-instance vptr to a shared static vtable — pointer-sized memory overhead per object plus an indirection on virtual dispatch.
Answer:
Each instance carries a hidden virtual pointer (vptr) to a per-class virtual table (vtable), adding a pointer-sized memory cost and an indirection on each virtual call.
When a class declares at least one virtual function, the compiler adds a hidden pointer to each object: the vptr. All objects of the same class point to the same static vtable — an array of function pointers, one per virtual function, shared by the whole class.
Two costs:
- Memory: one pointer per instance (typically 8 bytes), even if the class has no data members.
- Dispatch: a virtual call is not a direct call. The runtime loads the vptr, indexes into the vtable, and calls through the function pointer — an extra indirection compared to a non-virtual call. In practice this is cheap, but it exists, and it also defeats some compiler inlining opportunities.
The trade-off buys runtime polymorphism: the same virtual call on a Base* can resolve to different derived implementations depending on the object’s actual type. The interview answer: a per-instance vptr to a shared static vtable — pointer-sized memory overhead per object plus an indirection on virtual dispatch.
4. How does dynamic_cast signal failure when attempting an invalid downcast on pointer versus reference types?
Answer: A pointer cast returns nullptr; a reference cast throws std::bad_cast.
dynamic_cast performs a checked, runtime polymorphic cast (requires a polymorphic base — at least one virtual function). When the cast fails, the failure reporting depends on the target kind:
- Pointer:
dynamic_cast<T*>(p)returnsnullptr. Callers check for null. - Reference:
dynamic_cast<T&>(r)cannot return “nothing” — there’s no null reference in valid C++. So it throwsstd::bad_cast.
The asymmetry exists because references must always be valid; a failed reference cast has nowhere to put a “no result,” so it must raise an exception.
Base* b = new Derived;
if (Derived* d = dynamic_cast<Derived*>(b)) { /* ok */ }
Base& br = *b;
Derived& dr = dynamic_cast<Derived&>(br); // throws std::bad_cast if fails
The interview answer: pointer → nullptr; reference → std::bad_cast exception.
Answer:
A pointer cast returns nullptr; a reference cast throws std::bad_cast.
dynamic_cast performs a checked, runtime polymorphic cast (requires a polymorphic base — at least one virtual function). When the cast fails, the failure reporting depends on the target kind:
- Pointer:
dynamic_cast<T*>(p)returnsnullptr. Callers check for null. - Reference:
dynamic_cast<T&>(r)cannot return “nothing” — there’s no null reference in valid C++. So it throwsstd::bad_cast.
The asymmetry exists because references must always be valid; a failed reference cast has nowhere to put a “no result,” so it must raise an exception.
Base* b = new Derived;
if (Derived* d = dynamic_cast<Derived*>(b)) { /* ok */ }
Base& br = *b;
Derived& dr = dynamic_cast<Derived&>(br); // throws std::bad_cast if fails
The interview answer: pointer → nullptr; reference → std::bad_cast exception.
5. What is the size of an empty class struct in C++?
Answer: At least 1 byte (typically exactly 1).
An empty class has no data members, yet its objects must have a non-zero size. The reason is object identity: if empty objects were size 0, then two objects in an array could occupy the same address, and &a == &b would be true for distinct objects — which the standard forbids. Every object must have a distinct address.
So the compiler gives empty classes a minimum size of 1 byte. In arrays, each element gets its own byte, preserving distinct addresses.
A related optimization: the Empty Base Optimization (EBO) — an empty base class can take up no additional space when combined into a derived class. But a standalone empty object is at least 1 byte. The interview answer: non-zero, at least 1 byte, so distinct objects have distinct addresses.
Answer:
At least 1 byte (typically exactly 1).
An empty class has no data members, yet its objects must have a non-zero size. The reason is object identity: if empty objects were size 0, then two objects in an array could occupy the same address, and &a == &b would be true for distinct objects — which the standard forbids. Every object must have a distinct address.
So the compiler gives empty classes a minimum size of 1 byte. In arrays, each element gets its own byte, preserving distinct addresses.
A related optimization: the Empty Base Optimization (EBO) — an empty base class can take up no additional space when combined into a derived class. But a standalone empty object is at least 1 byte. The interview answer: non-zero, at least 1 byte, so distinct objects have distinct addresses.
6. Which keyword prevents implicit type conversions during single-argument constructor calls?
Answer: explicit.
A single-argument (or defaulted-with-args) constructor normally acts as an implicit conversion from its parameter type: passing an int where a String is expected would silently construct a String from the int. That implicit conversion is often surprising and sometimes dangerous (it can hide bugs or enable accidental conversions).
Marking the constructor explicit forbids implicit conversions:
String(int)— implicit conversion allowed:String s = 5;compiles.explicit String(int)— only explicit construction:String s(5);orString s = String(5);compiles;String s = 5;is an error.
explicit applies to constructors and conversion operators, and it’s used to require callers to state their intent. The interview answer: explicit prevents implicit single-argument conversions, forcing explicit construction.
Answer:
explicit.
A single-argument (or defaulted-with-args) constructor normally acts as an implicit conversion from its parameter type: passing an int where a String is expected would silently construct a String from the int. That implicit conversion is often surprising and sometimes dangerous (it can hide bugs or enable accidental conversions).
Marking the constructor explicit forbids implicit conversions:
String(int)— implicit conversion allowed:String s = 5;compiles.explicit String(int)— only explicit construction:String s(5);orString s = String(5);compiles;String s = 5;is an error.
explicit applies to constructors and conversion operators, and it’s used to require callers to state their intent. The interview answer: explicit prevents implicit single-argument conversions, forcing explicit construction.
7. What is the output of the following inheritance constructor sequence?
struct A {
A() { std::cout << "A"; }
};
struct B : A {
B() { std::cout << "B"; }
};
int main() {
B b;
}
Output: AB.
Object construction is bottom-up in the inheritance hierarchy: base class constructors run first, then derived. The base must be fully constructed (its invariants established) before the derived part can be initialized.
So for B b;:
A’s constructor runs first, printingA.- Then
B’s constructor body runs, printingB.
Output: AB.
The mirror rule applies to destruction, in reverse: derived destructor runs first, then base destructor (so the derived part is still intact when base cleanup runs). The interview answer: AB — base constructs first.
Answer:
AB.
Object construction is bottom-up in the inheritance hierarchy: base class constructors run first, then derived. The base must be fully constructed (its invariants established) before the derived part can be initialized.
So for B b;:
A’s constructor runs first, printingA.- Then
B’s constructor body runs, printingB.
Output: AB.
The mirror rule applies to destruction, in reverse: derived destructor runs first, then base destructor (so the derived part is still intact when base cleanup runs). The interview answer: AB — base constructs first.
8. What problem occurs if a base class destructor is NOT declared virtual when deleting a derived class object through a base class pointer?
Base* ptr = new Derived();
delete ptr;
Answer: Only the Base destructor runs — Derived’s destructor is never called — leaking resources owned by Derived and invoking undefined behavior.
delete works through the static type of the pointer you delete. If the base class destructor is non-virtual, the compiler has no way to dispatch to Derived’s destructor at runtime, so it calls Base::~Base() directly. Derived’s destructor (and any cleanup of members it allocated) is skipped — a resource leak, and per the standard, deleting through a non-virtual base destructor is undefined behavior.
struct Base { ~Base() {} }; // NON-virtual
struct Derived : Base { std::vector<int> v; };
Base* p = new Derived();
delete p; // Derived::~Derived() never runs → v leaks
The rule of thumb: any class intended as a base class should have a virtual destructor (and if it’s a polymorphic base with virtual functions, it virtually always should). If a class is never meant to be derived from, mark it final and keep the destructor non-virtual.
The interview answer: with a non-virtual base destructor, delete runs only Base::~Base(), skipping Derived cleanup and causing UB + leaks.
Answer:
Only the Base destructor runs — Derived’s destructor is never called — leaking resources owned by Derived and invoking undefined behavior.
delete works through the static type of the pointer you delete. If the base class destructor is non-virtual, the compiler has no way to dispatch to Derived’s destructor at runtime, so it calls Base::~Base() directly. Derived’s destructor (and any cleanup of members it allocated) is skipped — a resource leak, and per the standard, deleting through a non-virtual base destructor is undefined behavior.
struct Base { ~Base() {} }; // NON-virtual
struct Derived : Base { std::vector<int> v; };
Base* p = new Derived();
delete p; // Derived::~Derived() never runs → v leaks
The rule of thumb: any class intended as a base class should have a virtual destructor (and if it’s a polymorphic base with virtual functions, it virtually always should). If a class is never meant to be derived from, mark it final and keep the destructor non-virtual.
The interview answer: with a non-virtual base destructor, delete runs only Base::~Base(), skipping Derived cleanup and causing UB + leaks.
9. What is the structural purpose of a Non-Virtual Interface (NVI) idiom in class design?
Answer: Public non-virtual functions enforce invariants and pre/post-conditions, delegating the actual work to private/protected virtual functions that derived classes override.
The NVI idiom flips the usual design. Instead of making the interface itself virtual, you make the public entry point non-virtual and non-overridable, and make a hook virtual:
class Base {
public:
void doWork() { // public non-virtual
lock(); // common pre-conditions
doWorkImpl(); // virtual hook
unlock(); // common post-conditions
}
protected:
virtual void doWorkImpl() {} // derived overrides this
};
Benefits: the base class controls when and around what the virtual does — locking, invariant checks, logging, validation — and derived classes can’t accidentally skip those steps by overriding the public function. The interface is stable; the extension points are contained. The interview answer: public non-virtual methods enforce common behavior (locks, checks) and delegate to private/protected virtual hooks.
Answer:
Public non-virtual functions enforce invariants and pre/post-conditions, delegating the actual work to private/protected virtual functions that derived classes override.
The NVI idiom flips the usual design. Instead of making the interface itself virtual, you make the public entry point non-virtual and non-overridable, and make a hook virtual:
class Base {
public:
void doWork() { // public non-virtual
lock(); // common pre-conditions
doWorkImpl(); // virtual hook
unlock(); // common post-conditions
}
protected:
virtual void doWorkImpl() {} // derived overrides this
};
Benefits: the base class controls when and around what the virtual does — locking, invariant checks, logging, validation — and derived classes can’t accidentally skip those steps by overriding the public function. The interface is stable; the extension points are contained. The interview answer: public non-virtual methods enforce common behavior (locks, checks) and delegate to private/protected virtual hooks.
10. What design safety guarantee does the final specifier provide when appended to a class member method declaration?
Answer: It prevents derived classes from overriding that specific virtual function.
Marking a virtual function final locks it: no class further down the hierarchy may override it. Attempting to do so is a compile-time error.
class Base {
virtual void foo() final; // no override allowed below
};
class Derived : public Base {
void foo() override; // ERROR: Base::foo is final
};
The benefit is twofold: it documents design intent (“this behavior is locked”), and it enables devirtualization — since the compiler knows the function can’t be overridden, it can sometimes resolve the call statically instead of through the vtable. final can also be applied to a whole class (no one may derive from it). The interview answer: final prevents further overriding of that virtual function, enabling devirtualization.
Answer:
It prevents derived classes from overriding that specific virtual function.
Marking a virtual function final locks it: no class further down the hierarchy may override it. Attempting to do so is a compile-time error.
class Base {
virtual void foo() final; // no override allowed below
};
class Derived : public Base {
void foo() override; // ERROR: Base::foo is final
};
The benefit is twofold: it documents design intent (“this behavior is locked”), and it enables devirtualization — since the compiler knows the function can’t be overridden, it can sometimes resolve the call statically instead of through the vtable. final can also be applied to a whole class (no one may derive from it). The interview answer: final prevents further overriding of that virtual function, enabling devirtualization.
11. What happens when a pure virtual function (virtual void foo() = 0;) is declared in a class?
Answer: The class becomes abstract and cannot be instantiated directly.
= 0 after a virtual function declaration marks it pure. A class with at least one pure virtual function is abstract: you can’t create objects of it. It can still be used as a base class, and derived classes must provide concrete implementations of all pure virtual functions to become instantiable themselves.
Note: a pure virtual function can have a body in C++ (you can still define it and call it explicitly from derived classes), but the class remains abstract. Abstract classes serve as interfaces/contracts — they define the shape of behavior without being usable as concrete types. The interview answer: declaring a pure virtual function makes the class abstract — no direct instantiation, derived classes must override all pure virtuals to be concrete.
Answer:
The class becomes abstract and cannot be instantiated directly.
= 0 after a virtual function declaration marks it pure. A class with at least one pure virtual function is abstract: you can’t create objects of it. It can still be used as a base class, and derived classes must provide concrete implementations of all pure virtual functions to become instantiable themselves.
Note: a pure virtual function can have a body in C++ (you can still define it and call it explicitly from derived classes), but the class remains abstract. Abstract classes serve as interfaces/contracts — they define the shape of behavior without being usable as concrete types. The interview answer: declaring a pure virtual function makes the class abstract — no direct instantiation, derived classes must override all pure virtuals to be concrete.
12. What design restriction applies to static class member functions in C++?
Answer: Static member functions cannot be const, volatile, or virtual, and have no this pointer — so they can’t access non-static members.
A static member function belongs to the class type, not to any instance:
- No
thispointer — there’s no instance to be “current.” Consequently it can only access static data members and call other static functions. - Can’t be
const/volatile— those qualifiers describe the implicitthisobject, which doesn’t exist. - Can’t be
virtual— virtual dispatch is an instance mechanism; a static function isn’t invoked through an object.
Use static methods for behavior that doesn’t depend on instance state: factories, utilities, and operations on static data. The interview answer: no this, no non-static member access, and they can’t be const, volatile, or virtual.
Answer:
Static member functions cannot be const, volatile, or virtual, and have no this pointer — so they can’t access non-static members.
A static member function belongs to the class type, not to any instance:
- No
thispointer — there’s no instance to be “current.” Consequently it can only access static data members and call other static functions. - Can’t be
const/volatile— those qualifiers describe the implicitthisobject, which doesn’t exist. - Can’t be
virtual— virtual dispatch is an instance mechanism; a static function isn’t invoked through an object.
Use static methods for behavior that doesn’t depend on instance state: factories, utilities, and operations on static data. The interview answer: no this, no non-static member access, and they can’t be const, volatile, or virtual.
Premium Content
Unlock OOP, Virtual & Destructors and all premium lessons with a subscription.
From ₹199.99/year — See plans