Polymorphism in C++


Polymorphism in C++ Interview with follow-up questions

1. What is polymorphism in C++? Can you explain with an example?

Polymorphism means "many forms" — it lets you write code against a base-class interface and have the correct derived-class behaviour selected automatically. C++ supports two kinds:

  • Compile-time (static) polymorphism — function overloading, operator overloading, and templates (including C++20 concepts). Resolved at compile time; zero runtime overhead.
  • Runtime (dynamic) polymorphism — virtual functions dispatched through a vtable. Resolved at runtime based on the actual object type.

Example — runtime polymorphism

#include 
#include 
#include 

class Shape {
public:
    virtual void draw() const = 0;          // pure virtual
    virtual ~Shape() = default;
};

class Circle : public Shape {
public:
    void draw() const override { std::cout << "Drawing Circle\n"; }
};

class Rectangle : public Shape {
public:
    void draw() const override { std::cout << "Drawing Rectangle\n"; }
};

int main() {
    std::vector> shapes;
    shapes.push_back(std::make_unique());
    shapes.push_back(std::make_unique());

    for (const auto& s : shapes) {
        s->draw();   // correct override called at runtime
    }
}

Output:

Drawing Circle
Drawing Rectangle

How the vtable works

Every class with virtual functions carries a hidden vtable — an array of function pointers. Each object holds a hidden vptr pointing to its class's vtable. A virtual call like s->draw() dereferences the vptr, looks up the slot, and calls the right function — one extra indirection at runtime.

Key interview points

  • Always declare the base-class destructor virtual; otherwise delete shape_ptr when the pointer type is Shape* causes undefined behaviour.
  • Use override on every overriding function to catch signature mismatches at compile time.
  • Use final to prevent further overriding when appropriate.
  • Prefer std::unique_ptr / std::shared_ptr over raw new/delete (as shown above).
  • C++20 concepts offer a compile-time alternative to inheritance-based polymorphism when you do not need runtime dispatch.
↑ Back to top

Follow-up 1

What is the difference between compile-time and run-time polymorphism?

Compile-time polymorphism, also known as static polymorphism, is achieved through function overloading and operator overloading. The decision of which function or operator to call is made by the compiler at compile-time based on the number and types of arguments.

Run-time polymorphism, also known as dynamic polymorphism, is achieved through function overriding and virtual functions. The decision of which function to call is made at run-time based on the type of the object being referred to.

In summary, compile-time polymorphism is resolved at compile-time, while run-time polymorphism is resolved at run-time.

Follow-up 2

How does polymorphism promote code reusability?

Polymorphism promotes code reusability by allowing objects of different classes to be treated as objects of a common base class. This means that a single interface can be used to interact with multiple objects, regardless of their specific types.

For example, in the previous example, we can have a function that takes a Shape object as a parameter and calls its draw() function. This function can be used with any object that is derived from the Shape class, such as Circle or Rectangle. This promotes code reusability as the same function can be used with different types of objects.

Follow-up 3

Can you explain the concept of virtual functions in relation to polymorphism?

In C++, a virtual function is a member function of a base class that can be overridden in a derived class. When a function is declared as virtual in the base class, it is marked for dynamic binding. This means that the decision of which function to call is made at run-time based on the type of the object being referred to.

In the example provided earlier, the draw() function in the Shape class is declared as virtual. This allows the draw() function to be overridden in the derived classes Circle and Rectangle. When we call the draw() function on a Shape pointer that points to a Circle or Rectangle object, the appropriate version of the function is called based on the actual type of the object.

Follow-up 4

What is the role of the 'virtual' keyword in C++?

In C++, the virtual keyword is used to declare a member function as virtual in a base class. When a function is declared as virtual, it is marked for dynamic binding. This means that the decision of which function to call is made at run-time based on the type of the object being referred to.

In the example provided earlier, the draw() function in the Shape class is declared as virtual. This allows the draw() function to be overridden in the derived classes Circle and Rectangle. When we call the draw() function on a Shape pointer that points to a Circle or Rectangle object, the appropriate version of the function is called based on the actual type of the object.

2. How does polymorphism contribute to the flexibility of a C++ program?

Polymorphism lets you write algorithms and data structures that work against a stable interface (a base class or a concept) rather than a fixed set of concrete types. This produces several practical flexibility gains:

  1. Open for extension, closed for modification — you can add a new Triangle class without touching the rendering loop that iterates over Shape* objects. Existing, tested code is untouched.

  2. Uniform treatment of heterogeneous collections — a std::vector> can hold Circle, Rectangle, and Triangle objects simultaneously. The same loop calls the right draw() on each.

  3. Dependency inversion — high-level modules depend on abstractions, not concrete classes. Swapping a database backend or a rendering engine means providing a new derived class, not rewriting callers.

  4. Testability — you can inject mock or stub objects that share the base-class interface, making unit tests independent of the real implementation.

  5. Runtime configurability — the concrete type can be chosen at runtime (e.g., loaded from a config file or selected by the user), without recompiling the calling code.

Compile-time polymorphism via templates and concepts (C++20)

When runtime dispatch is not needed, templates and C++20 concepts provide the same flexibility with zero virtual-call overhead:

template 
concept Drawable = requires(const T& t) { t.draw(); };

template 
void render(const T& shape) { shape.draw(); }

Any type satisfying the Drawable concept works with render — no inheritance required.

Trade-off to mention

Runtime polymorphism (vtable dispatch) costs one pointer indirection and can inhibit inlining and branch-prediction. For hot inner loops, compile-time polymorphism (templates/concepts) or std::variant with std::visit may be preferable.

↑ Back to top

Follow-up 1

Can you provide a real-world scenario where polymorphism can be effectively used?

One real-world scenario where polymorphism can be effectively used is in a banking system. In this system, there can be different types of accounts such as savings account, checking account, and investment account. Each account type can have its own specific behavior and attributes. By using polymorphism, a single function can be written to process transactions for any type of account. This allows for easy addition of new account types in the future without modifying the existing code.

Follow-up 2

What are the potential issues that might arise if polymorphism is not used in such a scenario?

If polymorphism is not used in the banking system scenario, the code for processing transactions would need to be duplicated for each account type. This would result in code redundancy and make the system harder to maintain. Additionally, adding a new account type would require modifying the existing code, which increases the risk of introducing bugs or breaking existing functionality.

Follow-up 3

How does polymorphism relate to the concept of inheritance in C++?

Polymorphism is closely related to the concept of inheritance in C++. Inheritance allows a derived class to inherit the properties and behaviors of a base class. Polymorphism then allows objects of the derived class to be treated as objects of the base class, enabling code reuse and flexibility. Polymorphism is often achieved through virtual functions in C++, which are functions declared in the base class and overridden in the derived classes.

3. What is function overloading and how does it relate to polymorphism in C++?

Function overloading lets you define multiple functions with the same name but different parameter types or counts in the same scope. The compiler selects the best match at compile time based on the argument types — no runtime cost.

void print(int x)              { std::cout << "int: "    << x << '\n'; }
void print(double x)           { std::cout << "double: " << x << '\n'; }
void print(std::string_view s) { std::cout << "string: " << s << '\n'; }

print(42);       // calls print(int)
print(3.14);     // calls print(double)
print("hello");  // calls print(string_view)

Relation to polymorphism

Overloading is compile-time (static) polymorphism — the same name takes many forms depending on argument types, resolved entirely by the compiler. It contrasts with runtime (dynamic) polymorphism, which uses virtual functions dispatched through a vtable.

Function overloading Virtual function overriding
Resolution Compile time Runtime
Mechanism Name mangling + overload resolution vtable pointer indirection
Overhead None One indirect call per virtual dispatch
Keyword (none required) virtual, override

Key interview points

  • The return type alone does not distinguish overloads — the parameter list must differ.
  • Default arguments can interact subtly with overload resolution; be careful mixing the two.
  • Overloaded functions must be in the same scope; a derived-class function with the same name hides base-class overloads unless you use using Base::name;.
  • Operator overloading is another form of compile-time polymorphism (operator+, operator<<, etc.).
  • C++20 concepts add a third flavour: constrained templates that select a specialisation based on type properties, also resolved at compile time.
↑ Back to top

Follow-up 1

Can you provide an example of function overloading?

Sure! Here's an example of function overloading in C++:

#include 

void print(int num) {
    std::cout << "Printing an integer: " << num << std::endl;
}

void print(double num) {
    std::cout << "Printing a double: " << num << std::endl;
}

int main() {
    print(10);
    print(3.14);
    return 0;
}

In this example, we have two functions named "print". One takes an integer as a parameter, and the other takes a double. Depending on the type of argument passed to the "print" function, the corresponding overloaded function will be called.

Follow-up 2

What are the rules for function overloading in C++?

The rules for function overloading in C++ are as follows:

  1. The overloaded functions must have the same name.
  2. The overloaded functions must differ in the number or types of their parameters.
  3. The return type of the overloaded functions can be the same or different.

These rules ensure that the compiler can determine which overloaded function to call based on the arguments passed to it.

Follow-up 3

What is operator overloading and how does it differ from function overloading?

Operator overloading is a feature in C++ that allows operators such as +, -, *, /, etc. to be used with user-defined types. It allows the programmer to define how an operator should behave when applied to objects of a class. Operator overloading is different from function overloading in that it involves defining functions that are called when an operator is used on objects, rather than defining functions with the same name but different parameters.

For example, in function overloading, we might have multiple functions named "print" with different parameters. In operator overloading, we would define a function called "operator+" that specifies how the + operator should behave when used with objects of a class.

4. What is function overriding in C++ and how does it contribute to polymorphism?

Function overriding occurs when a derived class provides its own implementation of a virtual function declared in the base class, using the same name, parameter types, and const-qualification. The correct version is selected at runtime through virtual dispatch.

class Animal {
public:
    virtual void speak() const { std::cout << "...\n"; }
    virtual ~Animal() = default;
};

class Dog : public Animal {
public:
    void speak() const override { std::cout << "Woof\n"; }
};

class Cat : public Animal {
public:
    void speak() const override { std::cout << "Meow\n"; }
};

void makeNoise(const Animal& a) { a.speak(); }  // dispatches at runtime

Dog d;  Cat c;
makeNoise(d);  // Woof
makeNoise(c);  // Meow

How it enables polymorphism

The virtual keyword instructs the compiler to emit an indirect call through the vtable instead of a direct call. At runtime the vtable entry for the actual object type is used, so Animal& can trigger Dog::speak or Cat::speak without the caller knowing the concrete type.

Rules and best practices

  • Use override on every overriding function. If the signature does not match the base, override turns a silent typo (which creates a new virtual, not an override) into a compile error.
  • Use final to prevent a specific override from being overridden further, or on the class itself to seal the hierarchy.
  • Only functions declared virtual in the base class participate in dynamic dispatch; hiding a non-virtual function in a derived class does not override it.
  • The function signature — name, parameter types, and const/volatile qualifiers — must match exactly (return type may be covariant: a pointer/reference to a derived type).

Overriding vs. hiding

Situation What happens
Base: virtual f(), Derived: f() Override — vtable updated, dynamic dispatch works
Base: non-virtual f(), Derived: f() Hiding — base version still called through base pointer
↑ Back to top

Follow-up 1

Can you provide an example of function overriding?

Sure! Here's an example:

#include 

class Animal {
public:
    virtual void makeSound() {
        std::cout << "Animal makes a sound" << std::endl;
    }
};

class Dog : public Animal {
public:
    void makeSound() override {
        std::cout << "Dog barks" << std::endl;
    }
};

int main() {
    Animal* animal = new Dog();
    animal->makeSound(); // Output: Dog barks
    delete animal;
    return 0;
}

In this example, the Animal class has a virtual function makeSound(). The Dog class inherits from Animal and overrides the makeSound() function to provide its own implementation. When we create an object of Dog and call the makeSound() function through a pointer of type Animal, the overridden function in the Dog class is called, resulting in the output "Dog barks".

Follow-up 2

What are the rules for function overriding in C++?

The rules for function overriding in C++ are as follows:

  1. The function in the derived class must have the same name and the same parameter list as the function in the base class.
  2. The function in the derived class must have the same return type or a covariant return type (i.e., a derived class pointer or reference can be used as the return type).
  3. The function in the derived class must have the same access specifier or a more accessible access specifier (i.e., if the function in the base class is public, the function in the derived class can be public or protected, but not private).
  4. The function in the derived class must be declared with the override keyword to indicate that it is intended to override a function in the base class (optional, but recommended for clarity and to catch errors at compile-time).

If any of these rules are violated, the function in the derived class will not be considered as an override and will instead be treated as a new function.

Follow-up 3

How does function overriding relate to the concept of virtual functions in C++?

Function overriding is closely related to the concept of virtual functions in C++. Virtual functions are functions that are declared in the base class and can be overridden by derived classes. When a function is declared as virtual in the base class, it allows polymorphic behavior, which means that the appropriate function implementation is determined at runtime based on the actual type of the object.

In the example provided earlier, the makeSound() function in the Animal class is declared as virtual. This allows the Dog class to override the function and provide its own implementation. When we create an object of Dog and call the makeSound() function through a pointer of type Animal, the overridden function in the Dog class is called instead of the base class function. This is because the function is resolved at runtime based on the actual type of the object, rather than the type of the pointer or reference used to access the object.

The use of virtual functions and function overriding is essential in achieving polymorphism in C++.

5. What are the advantages and disadvantages of polymorphism in C++?

Advantages

  • Code reusability — generic algorithms written against a base-class interface work with any derived type, present or future, without modification.
  • Extensibility — adding a new type means writing a new class; callers are untouched (Open/Closed Principle).
  • Loose coupling — components depend on abstractions, not concrete types, making large codebases easier to evolve and test.
  • Uniform handling of heterogeneous data — a single container (std::vector>) can hold mixed types; iteration dispatches correctly.
  • Testability — mock or stub implementations can be injected through base-class pointers/references in unit tests.

Disadvantages

  • Runtime overhead — dynamic dispatch (vtable lookup + indirect call) costs one extra pointer dereference and blocks inlining. In tight loops this can matter.
  • Code size increase — each polymorphic class carries a vtable; heavy use of virtual functions and templates can inflate binary size.
  • Complexity — deep or tangled inheritance hierarchies are hard to reason about, debug, and refactor. The Liskov Substitution Principle is easy to violate silently.
  • Potential for object slicing — passing a derived object by value to a base-class parameter silently discards the derived part; always use pointers or references for polymorphic types.
  • Harder optimisation — virtual calls are harder for the compiler to inline or devirtualise, though modern compilers (and profile-guided optimisation) can sometimes devirtualise obvious cases.

Mitigations interviewers expect you to know

  • Use final to enable devirtualisation where a class will never be further derived.
  • Prefer compile-time polymorphism (templates, C++20 concepts) when runtime dispatch is not needed — zero overhead and enables inlining.
  • Use std::variant + std::visit as a type-safe alternative to virtual dispatch when the set of types is closed and known at compile time.
↑ Back to top

Follow-up 1

Can you provide a scenario where the use of polymorphism might not be ideal?

There are certain scenarios where the use of polymorphism might not be ideal. One such scenario is when performance is a critical factor. Polymorphism introduces some performance overhead due to the need for dynamic dispatch and virtual function calls. In performance-critical applications, such as real-time systems or high-performance computing, minimizing this overhead is crucial. In such cases, using polymorphism sparingly or finding alternative design patterns that can achieve the desired functionality without relying heavily on polymorphism might be a better approach.

Follow-up 2

How does polymorphism affect the performance of a C++ program?

Polymorphism can have an impact on the performance of a C++ program. The use of polymorphism introduces some performance overhead due to the need for dynamic dispatch and virtual function calls. Dynamic dispatch involves determining at runtime which function to call based on the actual type of the object, which can be slower compared to static dispatch where the function to call is determined at compile-time. Virtual function calls also incur additional overhead compared to non-virtual function calls.

However, it is important to note that the performance impact of polymorphism is usually negligible in most applications unless performance is a critical factor. Modern compilers and hardware optimizations can often mitigate the performance overhead to a great extent. It is also worth considering that the benefits of polymorphism, such as code reusability and flexibility, can outweigh the performance impact in many cases.

Follow-up 3

How can we mitigate the potential disadvantages of polymorphism?

There are several ways to mitigate the potential disadvantages of polymorphism:

  • Minimize dynamic dispatch: Dynamic dispatch, which is the process of determining at runtime which function to call based on the actual type of the object, can introduce performance overhead. Minimizing the use of virtual functions and dynamic dispatch can help mitigate this overhead. Consider using non-virtual functions or static polymorphism (template-based polymorphism) where applicable.
  • Use smart pointers: Polymorphism often involves working with pointers to base class objects. Using smart pointers, such as std::shared_ptr or std::unique_ptr, can help manage the lifetime of objects and avoid memory leaks.
  • Optimize hotspots: Identify performance-critical sections of code and optimize them using techniques such as loop unrolling, caching, or algorithmic improvements.
  • Profile and measure: Use profiling tools to identify performance bottlenecks and measure the impact of polymorphism on the overall performance of the program. This can help prioritize optimization efforts and ensure that the performance impact of polymorphism is within acceptable limits.

Live mock interview

Mock interview: Polymorphism in C++

Intermediate ~5 min Your own free AI key

Your voice and your AI key never touch our servers; the key stays in this browser and is sent only to Google. Only your round scores are saved to track progress.