← Back to Home

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:

  1. Compile-time Polymorphism (Static Binding)
  2. 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.