Polymorphism in OOP: What is It, Its Types, Examples, Benefits, & More
Updated on Feb 13, 2025 | 16 min read | 132.3k views
Share:
For working professionals
For fresh graduates
More
Updated on Feb 13, 2025 | 16 min read | 132.3k views
Share:
Table of Contents
You might be searching for ways to keep your code flexible without sacrificing clarity. Polymorphism in OOP can help with that. Think about a single function in a drawing program that knows how to draw circles, squares, or triangles. That function’s name stays the same, but the code behind it changes to match each shape. Polymorphism follows this pattern in code: you write a single interface, and it adapts to new requirements.
In this blog, you will explore how polymorphism works, learn about its types (compile-time and runtime) across multiple programming languages, compare their differences, and see real-world examples that bring the concept to life.
Polymorphism in OOP (object-oriented programming) is the ability for one reference or interface to represent different forms in your code. This feature makes it possible to handle various objects as if they share the same behavior, which creates adaptable programs that respond to new requirements without major rewrites.
You can better understand polymorphism in OOPS with example listed below:
Below is a snippet that demonstrates polymorphism in Java. Here is what happens in the code:
class Shape {
void draw() {
System.out.println("Drawing a generic shape");
}
}
class Circle extends Shape {
@Override
void draw() {
System.out.println("Drawing a circle");
}
}
class Square extends Shape {
@Override
void draw() {
System.out.println("Drawing a square");
}
}
public class Main {
public static void main(String[] args) {
Shape shape1 = new Circle();
Shape shape2 = new Square();
shape1.draw();
shape2.draw();
}
}
Output:
Drawing a circle
Drawing a square
This output appears because shape1 refers to a Circle and shape2 refers to a Square. Each call to draw() matches the actual class, which is why you see different results.
Also Read: Understanding Virtual Functions in Java: From Interfaces to Runtime Polymorphism
Polymorphism is vital for true object-oriented status because it allows different objects to share the same interface while keeping their own special behaviors.
You can explore its main benefits below:
You may encounter situations where the same action must adapt in distinct ways. Polymorphism addresses this by splitting into two categories: compile-time and runtime.
Compile-time polymorphism resolves decisions when you compile the code, often using method overloading or operator overloading. Runtime polymorphism, on the other hand, finalizes those decisions as the program runs commonly through method overriding.
Exploring these approaches will help you build adaptable systems that handle changing requirements without clutter.
Compile-time polymorphism, also known as static polymorphism or early binding, involves choosing the method to run when you compile your code. This choice depends on differences in parameters or operator definitions so the compiler knows exactly which version to call.
You get a predictable structure that keeps development clear and avoids guesswork when your program runs.
Method overloading is the most common example of compile-time polymorphism. You write multiple methods with the same name but vary their parameters by type, number, or order. This approach helps you reduce extra names in your code and makes your work more concise.
Another way to achieve compile-time polymorphism is operator overloading, where you redefine operators like + or - to handle user-defined data. Although each language has its own syntax for this, the goal remains the same: let your code handle multiple input types without creating completely new functions.
Below are several examples that show compile-time polymorphism in OOPS in action.
Example 1: Compile-time Polymorphism in Java Using Method Overloading
This example uses method overloading. It shows how the same method name can handle different numbers of parameters. The compiler selects the appropriate version at compile time.
class Calculator {
int add(int a, int b) {
return a + b;
}
int add(int a, int b, int c) {
return a + b + c;
}
}
public class Main {
public static void main(String[] args) {
Calculator calc = new Calculator();
System.out.println(calc.add(5, 10));
System.out.println(calc.add(2, 3, 4));
}
}
Output:
15
9
The output shows 15 for the two-argument call and 9 for the three-argument call. Each call matches a different add() method during compilation.
Example 2: Compile-time Polymorphism in C++ Using Method Overloading
This example also uses method overloading. It demonstrates how the same function name handles different parameters. The compiler decides which function to run based on the signature.
#include <iostream>
using namespace std;
class MathOps {
public:
int multiply(int x, int y) {
return x * y;
}
int multiply(int x, int y, int z) {
return x * y * z;
}
};
int main() {
MathOps ops;
cout << ops.multiply(3, 4) << endl;
cout << ops.multiply(2, 5, 2) << endl;
return 0;
}
Output:
12
20
The first call matches the two-parameter version and produces 12, while the second call matches the three-parameter version and yields 20.
Also Read: Function Overriding in C++ [Function Overloading vs Overriding with Examples]
Example 3: Compile-time Polymorphism in Python Using Operator Overloading
This example uses operator overloading. It modifies the + operator so that it can add objects of a custom class. The correct behavior is bound at compile time based on how Python handles the __add__ method.
class Distance:
def __init__(self, meters):
self.meters = meters
def __add__(self, other):
return Distance(self.meters + other.meters)
def display(self):
print(f"{self.meters} meters")
d1 = Distance(5)
d2 = Distance(10)
d3 = d1 + d2
d3.display()
Output:
15 meters
The output is 15 meters because the code adds the numeric parts from both objects and returns a new Distance instance.
Also Read: What is Polymorphism in Python? Polymorphism Explained with Examples
Example 4: Compile-time Polymorphism in Perl Using Operator Overloading
This example also showcases operator overloading. It customizes the + operator for a Complex class. Perl decides how to apply this operator at compile time when it detects + between Complex objects.
{
package Complex;
use overload '+' => \&add;
sub new {
my ($class, $r, $i) = @_;
my $self = { real => $r, imag => $i };
bless $self, $class;
return $self;
}
sub add {
my ($c1, $c2) = @_;
return Complex->new($c1->{real} + $c2->{real},
$c1->{imag} + $c2->{imag});
}
sub show {
my ($self) = @_;
print "$self->{real} + $self->{imag}i\n";
}
}
my $n1 = Complex->new(2, 3);
my $n2 = Complex->new(4, 5);
my $sum = $n1 + $n2;
$sum->show();
Output:
6 + 8i
The output appears because the add subroutine sums up the real and imaginary parts and prints them in a + bi form.
Runtime polymorphism, also referred to as dynamic binding or late binding, determines which method runs while your application is active. This approach often involves method overriding, where a subclass replaces or enhances a method inherited from its superclass.
The actual method that executes depends on the underlying object type rather than the reference type. This allows you to introduce new behavior in child classes without modifying the original parent class directly, although it can bring extra overhead because each call is resolved at runtime.
Method overriding occurs when a child class provides its own version of a method that already exists in the parent class. The child class method shares the same signature but includes different logic. If you call this method using a reference of the parent type, the child’s overridden method will run if the actual object is from the child class.
Take a look at the following examples to see runtime polymorphism at work in four different languages.
Example 1: Runtime Polymorphism in Java Using Method Overriding
Below is a small program that shows how a child class can override a parent class method at runtime. It revolves around a Device hierarchy.
class Device {
void start() {
System.out.println("Starting a generic device");
}
}
class Mobile extends Device {
@Override
void start() {
System.out.println("Switching on the mobile");
}
}
public class Main {
public static void main(String[] args) {
Device device = new Mobile();
device.start();
}
}
Output:
Switching on the mobile
The output shows the child class method in action. Even though the reference is declared as Device, the actual object is Mobile, so the overridden start() method is used.
Example 2: Runtime Polymorphism in C++ Using Method Overriding
This example focuses on a vehicle hierarchy. A Vehicle class has a move() method, and two subclasses override it to meet their needs.
#include <iostream>
using namespace std;
class Vehicle {
public:
virtual void move() {
cout << "Vehicle is moving in a general way" << endl;
}
};
class Car : public Vehicle {
public:
void move() override {
cout << "Car is driving on the road" << endl;
}
};
class Bike : public Vehicle {
public:
void move() override {
cout << "Bike is cycling down the path" << endl;
}
};
int main() {
Vehicle* v1 = new Car();
Vehicle* v2 = new Bike();
v1->move();
v2->move();
delete v1;
delete v2;
return 0;
}
Output:
Car is driving on the road
Bike is cycling down the path
The output confirms that each call to move() uses the method of the actual object. The pointer’s declared type is Vehicle*, but the real behavior depends on whether it points to a Car or Bike.
Example 3: Runtime Polymorphism in Python Using Method Overriding
This is a classic example of method overriding in Python. Here, a parent class called Book includes a method named read(). Two subclasses override this method with specific content.
When my_book.read() is called, Python looks at the actual type of my_book.
class Book:
def read(self):
print("Reading a general book")
class EBook(Book):
def read(self):
print("Reading an eBook on a device")
class PhysicalBook(Book):
def read(self):
print("Reading a physical copy with paper pages")
def start_reading(book):
book.read()
ebook = EBook()
pbook = PhysicalBook()
start_reading(ebook)
start_reading(pbook)
Output:
Reading an eBook on a device
Reading a physical copy with paper pages
The function start_reading(book) runs the overridden method of either EBook or PhysicalBook, depending on which object is passed.
Example 4: Runtime Polymorphism in Perl Using Method Overriding
Below is a scenario involving different payment methods. A Payment parent class has a method named pay(), and child classes override it to provide unique payment styles.
The actual subroutine call depends on the object’s real package.
{
package Payment;
sub new {
my $class = shift;
bless {}, $class;
}
sub pay {
print "Processing a generic payment\n";
}
}
{
package CreditPayment;
our @ISA = qw(Payment);
sub pay {
print "Paying with credit\n";
}
}
{
package CashPayment;
our @ISA = qw(Payment);
sub pay {
print "Paying with cash\n";
}
}
my $pay1 = CreditPayment->new();
my $pay2 = CashPayment->new();
my $payment_ref;
$payment_ref = $pay1;
$payment_ref->pay();
$payment_ref = $pay2;
$payment_ref->pay();
Output:
Paying with credit
Paying with cash
The calls to $payment_ref->pay() trigger the CreditPayment version first, followed by the CashPayment version. Perl looks up the object's actual package instead of relying on the declared parent class.
For in-depth knowledge, you can also check out upGrad's free tutorial, Overloading vs Overriding in Java.
upGrad’s Exclusive Software and Tech Webinar for you –
SAAS Business – What is So Different?
You might wonder how the specific timing of method selection affects your program’s structure and efficiency. Compile-time polymorphism fixes which method will run before execution begins, whereas runtime polymorphism decides the method call during the program’s operation.
Each approach offers distinct benefits in terms of speed, flexibility, and how child classes can refine behavior from a parent class.
Here is a table that highlights several key differences:
Aspect |
Compile-time Polymorphism |
Runtime Polymorphism |
Binding Time | Occurs at compilation, so the method to execute is fixed before execution. | Happens during execution, where the actual object decides which method runs. |
Primary Mechanism | Typically relies on overloading or operator redefinition. | Uses overriding, where a child class redefines a parent class method. |
Inheritance | Does not require inheritance, though both can coexist in the same project. | Relies on an inheritance chain, allowing children to supply tailored methods. |
Performance | Often faster, since the compiler resolves calls in advance. | May be slower because each method call is resolved at runtime. |
Flexibility | Less flexible, as behavior is tied to signatures chosen at compile time. | More flexible, because the object type at runtime dictates which method is used. |
Use Cases | Suited to handling different parameter lists or operator inputs under one method name. | Best for scenarios where child classes must alter or extend a shared method. |
Maintenance | Straightforward, but adding more overloads can clutter a class if many forms are needed. | Simplifies expansion by letting new subclasses override the parent’s methods. |
Type Checking | The compiler enforces correctness based on known parameter types or operator definitions. | The system checks the object type during execution, so mismatches surface at runtime. |
Also Read: Types of Polymorphism in Java [Static & Dynamic Polymorphism with Examples]
Polymorphism in OOPS lets different classes share consistent structures while still keeping unique implementations. This approach creates stronger software designs and opens the door to simpler expansions. It also lessens the need to craft entirely new function sets whenever your requirements change.
Below are some of the most noteworthy advantages:
Polymorphism can introduce hidden complexities that affect performance and code organization. While it promotes flexible designs, it also creates potential pitfalls if class hierarchies or overridden methods are not handled carefully.
Below are some common drawbacks:
You may encounter scenarios where one action takes many forms based on the situation. This versatility appears everywhere, from daily routines to business operations. Polymorphism mirrors these real-world patterns by allowing a single interface to produce unique outcomes.
Here are some everyday examples of polymorphism in OOP that illustrate this principle:
Many large companies and independent teams often rely on polymorphism to keep their systems structured while allowing new features to fit in quickly. It appears in fields ranging from online retail that deals with various payment methods to security platforms that validate different login procedures. This approach lowers the need for complete rewrites and maintains a cohesive style when developers add new functions.
Below is a table that highlights several industries and the ways polymorphism shapes their solutions:
Industry |
Use Case |
Why Polymorphism Help? |
E-commerce | Different shipping options, payment methods, and user roles | Lets you handle various actions (shipping, payment) under shared interfaces. |
Healthcare | Patient management with diverse record types | Simplifies additions of new record categories without changing established procedures. |
Gaming | Multiple character classes in role-playing titles | Lets each class override shared methods (attack, defend) for unique abilities. |
Finance | Various transaction types like checks, cards, or e-wallets | Allows each transaction type to extend a common interface and apply specific logic. |
Automotive | A single diagnostic system for different car models | Lets each model override standard checks without overhauling the entire application. |
Education | A unified portal for courses, labs, and extracurriculars | Makes it easy to add new sections or modules under one central structure. |
IT and software | Frameworks or microservices that share common interfaces | Enables teams to override shared methods, avoiding repeated code in large architectures. |
You’ve seen how polymorphism in OOP merges code structure with adaptability, letting you unify different behaviors under a single interface. By splitting it into compile-time and runtime forms, you keep your methods efficient and open to change.
Polymorphism promotes cleaner designs, easier updates, and broad real-world uses across industries like healthcare, e-commerce, and IT. If you want to strengthen your skills further, you can enroll in upGrad's free course on Java Object-oriented Programming.
With the right training and practice, you’ll build software that stays flexible when requirements shift — setting the stage for lasting success in your development journey.
Related Blogs and Tutorials:
Boost your career with our popular Software Engineering courses, offering hands-on training and expert guidance to turn you into a skilled software developer.
Master in-demand Software Development skills like coding, system design, DevOps, and agile methodologies to excel in today’s competitive tech industry.
Stay informed with our widely-read Software Development articles, covering everything from coding techniques to the latest advancements in software engineering.
Get Free Consultation
By submitting, I accept the T&C and
Privacy Policy
India’s #1 Tech University
Executive PG Certification in AI-Powered Full Stack Development
77%
seats filled
Top Resources