Imagine you have a remote control that can operate different devices: a TV, an air conditioner, and a music system. Even though you use the same remote, pressing the "Power" button turns on different devices in different ways. This is a simple example of polymorphism - the ability of something to take many forms.
In Object-Oriented Programming (OOP), polymorphism allows objects of different classes to be treated as objects of a common superclass, while still behaving differently based on their actual class. This flexibility is crucial for writing maintainable and scalable software.
Polymorphism works closely with other OOP concepts like inheritance (where classes inherit properties and behaviors from other classes) and abstraction (hiding complex details and showing only necessary features). Together, they enable programmers to design systems that are easier to extend and modify.
Formally, polymorphism means "many forms." In programming, it refers to the ability of a single interface or method to represent different underlying forms (data types or classes).
There are two main types of polymorphism in OOP:
| Aspect | Compile-time Polymorphism | Run-time Polymorphism |
|---|---|---|
| Also called | Method Overloading | Method Overriding |
| Binding time | At compile time (before program runs) | At run time (during program execution) |
| How it works | Multiple methods with same name but different parameters | Subclass provides specific implementation of superclass method |
| Inheritance required? | No | Yes |
| Example | Calculator class with multiple add() methods | Animal class with subclasses Dog and Cat overriding sound() |
Method overloading means having multiple methods in the same class with the same name but different parameter lists (different number, type, or order of parameters). The compiler decides which method to call based on the arguments passed.
Think of it like a Swiss Army knife: one tool handle, but many different blades and functions depending on what you need.
graph TD A[Call method foo()] --> B{Check parameters} B -->|1 int| C[Call foo(int)] B -->|2 ints| D[Call foo(int, int)] B -->|1 string| E[Call foo(string)]Here, the compiler looks at the method call and matches it to the correct method signature.
Method overriding occurs when a subclass provides its own version of a method already defined in its superclass. At run time, the program decides which method to invoke based on the actual object type, not the reference type.
This is like a universal remote that sends the "Power" command, but the device receiving it decides how to respond.
sequenceDiagram participant Caller participant Superclass participant Subclass Caller->>Superclass: call sound() alt Object is superclass Superclass-->>Caller: Superclass sound() else Object is subclass Subclass-->>Caller: Subclass overridden sound() end
This dynamic method dispatch is achieved through virtual functions or similar mechanisms in languages like Java and C++.
Step 1: Define the Calculator class with three add() methods:
class Calculator { int add(int a, int b) { return a + b; } int add(int a, int b, int c) { return a + b + c; } double add(double a, double b) { return a + b; }} Step 2: Use the methods:
Calculator calc = new Calculator();System.out.println(calc.add(5, 10)); // Calls add(int, int)System.out.println(calc.add(1, 2, 3)); // Calls add(int, int, int)System.out.println(calc.add(2.5, 3.5)); // Calls add(double, double)
Step 3: The compiler selects the correct method based on parameters.
Answer: Output will be:
15
6
6.0
Step 1: Define the base class and subclasses:
class Animal { void sound() { System.out.println("Some generic animal sound"); }}class Dog extends Animal { @Override void sound() { System.out.println("Bark"); }}class Cat extends Animal { @Override void sound() { System.out.println("Meow"); }} Step 2: Create Animal references to Dog and Cat objects:
Animal a1 = new Dog();Animal a2 = new Cat();a1.sound(); // Calls Dog's sound()a2.sound(); // Calls Cat's sound()
Step 3: At run time, the overridden methods in Dog and Cat are called, not the base class method.
Answer: Output will be:
Bark
Meow
Step 1: Define the Shape class and subclasses:
class Shape { void draw() { System.out.println("Drawing a shape"); }}class Circle extends Shape { @Override void draw() { System.out.println("Drawing a circle"); }}class Rectangle extends Shape { @Override void draw() { System.out.println("Drawing a rectangle"); }}class Triangle extends Shape { @Override void draw() { System.out.println("Drawing a triangle"); }} Step 2: Store different shapes in an array and call draw():
Shape[] shapes = {new Circle(), new Rectangle(), new Triangle()};for (Shape s : shapes) { s.draw();} Step 3: Each object's draw() method is called according to its actual type.
Answer: Output will be:
Drawing a circle
Drawing a rectangle
Drawing a triangle
class Parent { void show() { System.out.println("Parent show"); }}class Child extends Parent { void show() { System.out.println("Child show"); }}public class Test { public static void main(String[] args) { Parent p = new Child(); p.show(); }} Step 1: Identify polymorphism type.
This is method overriding (run-time polymorphism) because the Child class provides its own implementation of show(), and the Parent reference points to a Child object.
Step 2: Predict output.
At run time, the Child's show() method is called.
Answer: Output will be:
Child show
Step 1: Define the interface and implementing classes:
interface Drawable { void draw();}class Line implements Drawable { public void draw() { System.out.println("Drawing a line"); }}class Circle implements Drawable { public void draw() { System.out.println("Drawing a circle"); }}class Square implements Drawable { public void draw() { System.out.println("Drawing a square"); }} Step 2: Create an array of Drawable and call draw():
Drawable[] drawables = {new Line(), new Circle(), new Square()};for (Drawable d : drawables) { d.draw();} Step 3: Each object's draw() method is called according to its class.
Answer: Output will be:
Drawing a line
Drawing a circle
Drawing a square
When to use: When distinguishing between types of polymorphism in exam questions.
When to use: When analyzing code snippets with multiple methods of the same name.
When to use: When determining if a method is overridden in a subclass.
When to use: For conceptual clarity and memory retention.
When to use: Before attempting competitive exam questions on OOP.
Progress tracking is paywalled — subscribe to mark subtopics as understood and save your streak.
Go to practice →