
© Wright Studio / Shutterstock.com
In the software development realm, object-oriented programming has been the dominant paradigm for decades now. This is because programmers benefit from tools like polymorphism, that help their ideas come to life more easily.
In this article, you will learn all about polymorphism: what it is, how it works, and its best practices. We promise you that understanding this fundamental concept will allow you to see your code in new ways and push you further along your programming journey.
What is Polymorphism?
According to the etymology of the word, we can define polymorphism as the capacity of information to be communicated in more than one way.
For example, you can take a chair and also categorize it as a seat, or furniture. These concepts are all correct and they all symbolize the same item.
We can already appreciate how interesting polymorphism can be as a theoretical concept, but in the OOPs realm, we can take it to a whole other level.
When programming, we have two categories of polymorphism: compile-time polymorphism and runtime polymorphism. Let’s dig a little deeper into each category.
Compile-Time Polymorphism
As you can guess, compile-time polymorphism occurs during the compiling of our code and is achieved by “method overloading”. This means that we create more than one function or operator with the same name.
If we create several functions using the same denomination, we consider them overloaded. The functions differentiate from each other because of the arguments in the function body.
Therefore, the return we receive will vary depending on what the argument does to the values we send to the overloaded functions. This is called function overloading and we can see an example below:
#include
using namespace std;
class Example {
public:
void func(int x)
{
cout << "value of x is " << x << endl;
}
void func(double x)
{
cout << "value of x is " << x << endl;
}
void func(int x, int
{
cout << "value of x and y is " << x << ", " << y
<< endl;
}};
int main()
{
Example obj1;
obj1.func(7);
obj1.func(9.132);
obj1.func(85, 64);
return 0;
}
The output will look something like this:

©History-Computer.com
The above example shows how we can define three functions using the same name. In this case, what distinguishes them is the type and amount of parameters used. At the same time, in the driver code, we select what function to use depending on what parameter we pass on it.
Function overloading is a very valuable polymorphism feature, especially when we need to execute and process different results in a single take.
Always remember that for overloading to work the function name should be identical but the arguments should differ from each other.
Operator Overloading
When building programs using OOPs, we also have the operator overloading path. How does this work? It depends on what programming language we are working with. But basically, operator overloading occurs when an operator like “+” or “-” is applied to work with data types that aren’t numbers, like strings.
In C++ operators can do special combinations using user-defined classes. For example, we can concatenate a string just by placing the “+” between each string.
Here is an example for you to read through and understand the intuitive aspect of this tool:
#include
using namespace std;
class Fraction {
private:
int numerator;
int denominator;
public:
Fraction(int num = 0, int den = 1) : numerator(num), denominator(den) {}
// Overloading the addition operator (+)
Fraction operator+(const Fraction& other) {
int num = numerator * other.denominator + other.numerator * denominator;
int den = denominator * other.denominator;
return Fraction(num, den);
}
// Overloading the subtraction operator (-)
Fraction operator-(const Fraction& other) {
int num = numerator * other.denominator - other.numerator * denominator;
int den = denominator * other.denominator;
return Fraction(num, den);
}
// Overloading the output stream operator (<<)
friend ostream& operator<<(ostream& out, const Fraction& fraction) {
out << fraction.numerator << "/" << fraction.denominator;
return out;
}
};
int main() {
Fraction fraction1(1, 2);
Fraction fraction2(3, 4);
Fraction sum = fraction1 + fraction2;
Fraction difference = fraction1 - fraction2;
cout << "Fraction 1: " << fraction1 << endl;
cout << "Fraction 2: " << fraction2 << endl;
cout << "Sum: " << sum << endl;
cout << "Difference: " << difference << endl;
return 0;
}
Here, we have a “main()” function that displays operator overloading in a very tidy manner.
In the above example, “fraction” has a numerator and denominator. We can perform operations on “fraction” objects by overloading the addition and subtraction operators. We also overload the output stream operator, allowing us to print “fraction” objects directly using the “cout” stream.
So, operator overloading allows us to extend the regular use that operators have and gain access to a whole spectrum of new tools. There is a plethora of operators you can overload. Make sure to check the official C++ documentation to expand on the topic!

©History-Computer.com
Runtime Polymorphism
Runtime polymorphism, also known as dynamic polymorphism and late binding, occurs during program runtime. The Runtime state is the last step of the program’s lifecycle, from the execution until termination.
Function overriding (do not mistake it for overloading) is the perfect example of runtime polymorphism applied in the structure of a program. Overriding takes a base class function and “recycles” it to create another class with the same return type and parameters.
#include
using namespace std;
class base {
public:
virtual void print() {
cout << "print base class" << endl;
}
void show() {
cout << "show base class" << endl;
}
};
class derived : public base {
public:
void print() {
cout << "print derived class" << endl;
}
void show() {
cout << "show derived class" << endl;
}
};
int main() {
base* bptr;
derived d;
bptr = &d;
bptr->print();
bptr->show();
return 0;
}
The output will look like this:

©History-Computer.com
Contrary to overloading, function overriding can only run once (because it works during Runtime) and needs inheritance to function.
Usually, the function created by overriding is called a virtual function. They are worth mentioning because it’s the result of the whole process of declaration in the base class and definition in the resulting class.
Virtual functions work with objects, they are (and must be) dynamic, and they execute during runtime. But what role does inheritance play in all this?
Polymorphism and Inheritance: What’s the Difference?
Inheritance is another OOPs feature that extends a function’s possibilities. Like polymorphism, it also works by creating new classes from existing ones but it takes a different approach.
As the name suggests, the new class (also known as “derived class” or “child class”) “inherits” the return type and parameter of the parent class. We can add new parameters to the child class without changing the parent class, creating a variation of it.
Even if they sound alike, polymorphism and inheritance have some key differences that will help us decide which one is better for the job.
When we want a customizable code with a wide variety of functions that do a lot of different tasks, we should approach the job using inheritance. We can start with a parent class that contains methods, functions, and other parameters and then start building our architecture from that.
On the other hand, polymorphism can save us a lot of time by allowing us to create a group of functions with the same name so that when they are called, they execute at the same time and return the values we need.
Polymorphism does not give our code the complexity that class inheritance has, but instead had some customization options that we already saw. The objects can decide which function form to adopt and when (Compile-time or Runtime).
Polymorphism in C++: Fully Explained
Polymorphism might seem like a complex concept, but it’s worth dedicating time to it. We saw what it can do for our code, and when it’s best to apply it. It’s a fundamental topic if we are going to dedicate ourselves to modern programming languages.
Now we know how to apply polymorphism using function overloading and overriding, helping us to take regular functions to another level. We also learned about compile-time and runtime, and what they mean to the lifecycle of our programs.
Additionally, we got into the specifics of inheritance and how it differs from polymorphism.
As we always recommend, it’s a great practice to check C++’s official documentation to enrich the concepts we just learned, and also take the time to replicate and extend the code examples we just gave you.