Polymorphism
Polymorphism means “many forms.” In Java, it allows the same method name or interface to behave differently based on context. Polymorphism improves flexibility, extensibility, and maintainability and is central to OOP design.
Java supports two types of polymorphism:
- Compile-time Polymorphism (Static Binding)
- Runtime Polymorphism (Dynamic Binding)
What Is Polymorphism?
- One interface, multiple implementations
- Same method name, different behavior
- Enables loose coupling and extensibility
1️⃣ Compile-time Polymorphism (Static Polymorphism)
Compile-time polymorphism is achieved using method overloading. The method call is resolved at compile time based on the method signature (name + parameters).
Key Characteristics
- Achieved using method overloading
- Resolved at compile time
- Faster execution
- No inheritance required
Example: Method Overloading
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;
}
}
Why compile-time? The compiler decides which add() method to call based on arguments.
Compile-time Polymorphism Rules
- Same method name
- Different parameter list
- Return type alone is not enough
- Binding happens before execution
2️⃣ Runtime Polymorphism (Dynamic Polymorphism)
Runtime polymorphism is achieved using method overriding and inheritance. The method call is resolved at runtime based on the actual object, not the reference type.
Key Characteristics
- Achieved using inheritance + overriding
- Resolved at runtime
- Requires IS-A relationship
- Supports dynamic behavior
Example: Method Overriding
class Animal {
void sound() {
System.out.println("Animal sound");
}
}
class Dog extends Animal {
@Override
void sound() {
System.out.println("Dog barks");
}
}
Animal a = new Dog();
a.sound(); // Dog barks
- Reference type → Animal
- Object type → Dog
- Method executed → Dog’s implementation
Why Runtime Polymorphism Works
- JVM uses Dynamic Method Dispatch
- Method resolution depends on object type at runtime
- Enables plug-and-play behavior
Compile-time vs Runtime Polymorphism (Interview Favorite)
| Aspect | Compile-time | Runtime |
|---|---|---|
| Also called | Static Polymorphism | Dynamic Polymorphism |
| Achieved by | Method Overloading | Method Overriding |
| Binding time | Compile time | Runtime |
| Inheritance required | ❌ No | ✅ Yes |
| Method signature | Different | Same |
| Performance | Faster | Slightly slower |
| Flexibility | Limited | High |
Polymorphism with Parent Reference
Parent p = new Child();
- Access decided by reference type
- Method execution decided by object type
Polymorphism Limitations
- Static methods → Not overridden (method hiding)
- Final methods → Cannot be overridden
- Private methods → Not overridden
- Variables → No polymorphism (resolved by reference type)
class A {
int x = 10;
}
class B extends A {
int x = 20;
}
A obj = new B();
System.out.println(obj.x); // 10
Real-World Example (Very Interview-Friendly)
class Payment {
void pay() {
System.out.println("Generic payment");
}
}
class CreditCard extends Payment {
void pay() {
System.out.println("Credit card payment");
}
}
class UPI extends Payment {
void pay() {
System.out.println("UPI payment");
}
}
Payment p = new CreditCard();
p.pay(); // Credit card payment
- Easy to add new payment types
- No code change in existing logic
Benefits of Polymorphism
- Loose coupling
- Easy extensibility
- Cleaner code
- Better abstraction
- Supports Open-Closed Principle
Common Beginner Mistakes
- Confusing overloading with overriding
- Expecting variable polymorphism
- Overriding static methods
- Forgetting inheritance
- Not using @Override
Polymorphism Examples (Compile-Time vs Runtime)
1. Polymorphism via Method Overriding (Core Example)
class Animal {
void sound() {
System.out.println("Animal sound");
}
}
class Dog extends Animal {
void sound() {
System.out.println("Dog barks");
}
public static void main(String[] args) {
Animal a = new Dog();
a.sound();
}
}
Explanation
- Parent reference → child object
- Runtime method binding
Output: Dog barks
2. Compile-Time Polymorphism (Method Overloading)
class Calc {
int add(int a, int b) {
return a + b;
}
int add(int a, int b, int c) {
return a + b + c;
}
public static void main(String[] args) {
Calc c = new Calc();
System.out.println(c.add(2, 3));
System.out.println(c.add(2, 3, 4));
}
}
Explanation
- Method resolved at compile time
Output:
5 9
3. Runtime Polymorphism with Multiple Child Classes
class Shape {
void draw() {
System.out.println("Drawing shape");
}
}
class Circle extends Shape {
void draw() {
System.out.println("Drawing circle");
}
}
class Square extends Shape {
void draw() {
System.out.println("Drawing square");
}
}
class Test {
public static void main(String[] args) {
Shape s1 = new Circle();
Shape s2 = new Square();
s1.draw();
s2.draw();
}
}
Explanation
- Same reference type, different behaviors
Output:
Drawing circle Drawing square
4. Polymorphism Using Method Parameter
class Printer {
void print(String msg) {
System.out.println(msg);
}
}
class Test {
static void call(Printer p) {
p.print("Hello");
}
public static void main(String[] args) {
call(new Printer());
}
}
Explanation
- Method accepts parent type
- Behavior depends on actual object
5. Polymorphism Using Return Type
class Animal {
void sound() {
System.out.println("Animal");
}
}
class Cat extends Animal {
void sound() {
System.out.println("Meow");
}
}
class Test {
static Animal getAnimal() {
return new Cat();
}
public static void main(String[] args) {
Animal a = getAnimal();
a.sound();
}
}
Explanation
- Returned object decides behavior
Output: Meow
6. Polymorphism with super Reference
class A {
void show() {
System.out.println("A");
}
}
class B extends A {
void show() {
System.out.println("B");
}
void test() {
super.show();
}
public static void main(String[] args) {
new B().test();
}
}
Explanation
- super bypasses polymorphism
Output: A
7. Polymorphism and Field Access (Trap)
class A {
int x = 10;
}
class B extends A {
int x = 20;
public static void main(String[] args) {
A a = new B();
System.out.println(a.x);
}
}
Explanation
- Variables are not polymorphic
Output: 10
8. Method Polymorphism vs Variable Access
class A {
int x = 10;
void show() {
System.out.println(x);
}
}
class B extends A {
int x = 20;
void show() {
System.out.println(x);
}
public static void main(String[] args) {
A a = new B();
a.show();
System.out.println(a.x);
}
}
Explanation
- Method → runtime
- Variable → compile time
Output:
20 10
9. Polymorphism with Abstract Class
abstract class Vehicle {
abstract void run();
}
class Car extends Vehicle {
void run() {
System.out.println("Car running");
}
public static void main(String[] args) {
Vehicle v = new Car();
v.run();
}
}
Explanation
- Abstract method enables runtime polymorphism
Output: Car running
10. Polymorphism with Interface
interface Payment {
void pay();
}
class CardPayment implements Payment {
public void pay() {
System.out.println("Paid by card");
}
}
class Test {
public static void main(String[] args) {
Payment p = new CardPayment();
p.pay();
}
}
Explanation
- Interface reference → implementation object
Output: Paid by card
11. Polymorphism in Loop (Common Interview Case)
class Animal {
void sound() {
System.out.println("Animal");
}
}
class Dog extends Animal {
void sound() {
System.out.println("Dog");
}
}
class Cat extends Animal {
void sound() {
System.out.println("Cat");
}
}
class Test {
public static void main(String[] args) {
Animal[] animals = { new Dog(), new Cat() };
for (Animal a : animals) {
a.sound();
}
}
}
Explanation
- Dynamic dispatch in loop
Output:
Dog Cat
12. Polymorphism with Casting
class A {
void show() {
System.out.println("A");
}
}
class B extends A {
void test() {
System.out.println("B specific");
}
public static void main(String[] args) {
A a = new B();
B b = (B) a;
b.test();
}
}
Explanation
- Downcasting enables child-specific methods
Output: B specific
13. Invalid Polymorphic Cast (Runtime Error)
class A {}
class B extends A {}
class Test {
public static void main(String[] args) {
A a = new A();
// B b = (B) a; // ClassCastException
}
}
Explanation
- Object type must match cast
14. Polymorphism and instanceof
class A {}
class B extends A {}
class Test {
public static void main(String[] args) {
A a = new B();
System.out.println(a instanceof B);
}
}
Explanation
- Runtime type check
Output: true
15. Static Methods Are NOT Polymorphic
class A {
static void show() {
System.out.println("A");
}
}
class B extends A {
static void show() {
System.out.println("B");
}
public static void main(String[] args) {
A a = new B();
a.show();
}
}
Explanation
- Static binding
Output: A
16. Polymorphism with Final Method
class A {
final void show() {
System.out.println("Final method");
}
}
class B extends A {
// cannot override
}
Explanation
- final prevents polymorphism via overriding
17. Polymorphism with Constructor (Not Applicable)
class A {
A() {
System.out.println("A constructor");
}
}
class B extends A {
B() {
System.out.println("B constructor");
}
public static void main(String[] args) {
A a = new B();
}
}
Explanation
- Constructors are not polymorphic
Output:
A constructor B constructor
18. Polymorphism in Real-World Example (Logger)
interface Logger {
void log(String msg);
}
class FileLogger implements Logger {
public void log(String msg) {
System.out.println("File: " + msg);
}
}
class Test {
static void process(Logger l) {
l.log("Started");
}
public static void main(String[] args) {
process(new FileLogger());
}
}
Explanation
- Behavior varies by implementation
Output: File: Started
19. Compile-Time vs Runtime Polymorphism (Side by Side)
class Demo {
void show(int x) {
System.out.println("int");
}
void show(String s) {
System.out.println("String");
}
public static void main(String[] args) {
Demo d = new Demo();
d.show(10);
d.show("Java");
}
}
Explanation
- Compile-time polymorphism
Output:
int String
20. Interview Summary – Polymorphism
class A {
void show() {
System.out.println("A");
}
}
class B extends A {
void show() {
System.out.println("B");
}
public static void main(String[] args) {
A obj = new B();
obj.show();
}
}
Explanation
- One interface, many forms
- Runtime method binding
- Core OOP concept
Output: B
Interview-Ready Answers
Short Answer
Polymorphism allows the same method to behave differently based on context.
Detailed Answer
In Java, polymorphism is achieved in two ways: compile-time polymorphism using method overloading, where method resolution happens at compile time, and runtime polymorphism using method overriding, where the JVM determines the method call at runtime based on the object type.
Key Takeaway
Compile-time polymorphism decides behavior early. Runtime polymorphism decides behavior dynamically. Polymorphism is the heart of flexible and extensible Java design and is essential for frameworks, APIs, and real-world applications.