What is polymorphism in Java?

Published on April 5, 2026
#Java #InterviewQuestions #OOP #Polymorphism
Java code on screen

The Interview Question

"What is polymorphism in Java?"


Polymorphism in Java is one of the core principles of Object-Oriented Programming that allows objects of different types to be treated as objects of a common supertype. The word polymorphism means "many forms," and it occurs when we have many classes related by inheritance.


In Java, polymorphism is primarily implemented through method overriding (runtime polymorphism) and method overloading (compile-time polymorphism). It enables a single interface to represent different underlying forms (data types) and allows methods to do different things based on the object that calls them, providing flexibility and reusability in code.



Polymorphism is often described as one of the most powerful features of object-oriented programming. When I first encountered polymorphism, it seemed like a complex concept, but it's actually something we use intuitively once we understand it.


At its heart, polymorphism allows us to write more flexible and reusable code by letting us work with objects at a higher level of abstraction. Think of it as the ability to interact with different objects through a common interface without worrying about their specific types.


For instance, when driving, you operate different vehicles (car, truck, motorcycle) using the same basic interface (steering, accelerating, braking) despite each vehicle having unique implementations of these operations. That's polymorphism in the real world!



Java supports two main types of polymorphism:


4.1 Compile-time Polymorphism (Static Binding)


Also known as method overloading, this occurs when multiple methods in the same class have the same name but different parameters. The compiler determines which method to call based on the method signature and argument types at compile time.


class Calculator {
              // Method with two int parameters
              int add(int a, int b) {
                  return a + b;
              }
              
              // Method with three int parameters (overloaded)
              int add(int a, int b, int c) {
                  return a + b + c;
              }
              
              // Method with two double parameters (overloaded)
              double add(double a, double b) {
                  return a + b;
              }
          }

Note

Method overloading is sometimes not considered "true" polymorphism since it's resolved at compile time rather than runtime. However, it does allow a single method name to take multiple forms, which is the essence of polymorphism.


4.2 Runtime Polymorphism (Dynamic Binding)


This is the more powerful form of polymorphism, implemented through method overriding. It allows a subclass to provide a specific implementation of a method that is already defined in its parent class. The JVM determines which method to call at runtime based on the actual object type, not the reference type.


For runtime polymorphism to work, you need:


  • Inheritance (an IS-A relationship)
  • Method overriding (same method signature in subclass as in parent class)
  • A reference variable of the parent class that refers to a child class object

class Animal {
              void makeSound() {
                  System.out.println("Some generic animal sound");
              }
          }

          class Dog extends Animal {
              @Override
              void makeSound() {
                  System.out.println("Woof");
              }
          }

          class Cat extends Animal {
              @Override
              void makeSound() {
                  System.out.println("Meow");
              }
          }

          // Usage:
          Animal myPet = new Dog(); // Animal reference, Dog object
          myPet.makeSound();       // Outputs: "Woof"

          myPet = new Cat();       // Now myPet refers to a Cat object
          myPet.makeSound();       // Outputs: "Meow"

This is the essence of runtime polymorphism—the method called is determined by the actual object type at runtime, not the reference type. The Animal reference myPet calls different implementations of makeSound() depending on which object it's referencing.


4.3 Polymorphism through Interfaces


Interfaces are another powerful way to implement polymorphism in Java. By defining a common interface, we can create multiple implementations that can be used interchangeably. This is often used for dependency injection and more flexible architecture.


interface Drivable {
              void accelerate();
              void brake();
          }

          class Car implements Drivable {
              @Override
              public void accelerate() {
                  System.out.println("Car is accelerating");
              }
              
              @Override
              public void brake() {
                  System.out.println("Car is braking");
              }
          }

          class Truck implements Drivable {
              @Override
              public void accelerate() {
                  System.out.println("Truck is accelerating slowly");
              }
              
              @Override
              public void brake() {
                  System.out.println("Truck is braking with air brakes");
              }
          }

          // Usage:
          Drivable vehicle = new Car();
          vehicle.accelerate();    // Outputs: "Car is accelerating"

          vehicle = new Truck();
          vehicle.accelerate();    // Outputs: "Truck is accelerating slowly"

Key Benefits of Polymorphism
  • Code Flexibility: Write code that can work with objects of multiple types
  • Extensibility: Easily add new derived classes without changing existing code
  • Simplified Maintenance: Change implementation details without affecting the interface
  • Cleaner Design: Implement more elegant solutions with less code duplication
  • Better Organization: Group related classes and define their relationships clearly


Let's look at a comprehensive example that demonstrates polymorphism in Java through a real-world scenario of a shape hierarchy:


// Base class
              abstract class Shape {
                  private String color;
                  
                  public Shape(String color) {
                      this.color = color;
                  }
                  
                  // Common method for all shapes
                  public String getColor() {
                      return color;
                  }
                  
                  // Abstract method that each shape must implement
                  public abstract double calculateArea();
                  
                  // Method that can be overridden
                  public void displayInfo() {
                      System.out.println("This is a " + color + " shape.");
                  }
              }

              // Circle subclass
              class Circle extends Shape {
                  private double radius;
                  
                  public Circle(String color, double radius) {
                      super(color);
                      this.radius = radius;
                  }
                  
                  @Override
                  public double calculateArea() {
                      return Math.PI * radius * radius;
                  }
                  
                  @Override
                  public void displayInfo() {
                      System.out.println("This is a " + getColor() + " circle with radius " + radius);
                  }
              }

              // Rectangle subclass
              class Rectangle extends Shape {
                  private double width;
                  private double height;
                  
                  public Rectangle(String color, double width, double height) {
                      super(color);
                      this.width = width;
                      this.height = height;
                  }
                  
                  @Override
                  public double calculateArea() {
                      return width * height;
                  }
                  
                  @Override
                  public void displayInfo() {
                      System.out.println("This is a " + getColor() + " rectangle with width " + 
                                        width + " and height " + height);
                  }
              }

              // Triangle subclass
              class Triangle extends Shape {
                  private double base;
                  private double height;
                  
                  public Triangle(String color, double base, double height) {
                      super(color);
                      this.base = base;
                      this.height = height;
                  }
                  
                  @Override
                  public double calculateArea() {
                      return 0.5 * base * height;
                  }
                  
                  @Override
                  public void displayInfo() {
                      System.out.println("This is a " + getColor() + " triangle with base " + 
                                        base + " and height " + height);
                  }
              }

              // Demonstration of polymorphism
              public class PolymorphismDemo {
                  public static void main(String[] args) {
                      // Array of Shape references holding different shape objects
                      Shape[] shapes = new Shape[3];
                      shapes[0] = new Circle("Red", 5.0);
                      shapes[1] = new Rectangle("Blue", 4.0, 6.0);
                      shapes[2] = new Triangle("Green", 3.0, 4.0);
                      
                      // Polymorphic behavior - same method call, different implementations
                      System.out.println("Shape Information:");
                      for (Shape shape : shapes) {
                          shape.displayInfo();
                          System.out.println("Area: " + shape.calculateArea());
                          System.out.println();
                      }
                      
                      // Demonstrating polymorphism with a method
                      System.out.println("Total Area Calculation:");
                      System.out.println("Total area: " + calculateTotalArea(shapes));
                  }
                  
                  // Method that works with any Shape subclass
                  public static double calculateTotalArea(Shape[] shapes) {
                      double totalArea = 0;
                      for (Shape shape : shapes) {
                          totalArea += shape.calculateArea();
                      }
                      return totalArea;
                  }
              }

This example demonstrates several important aspects of polymorphism:


  1. Abstract Base Class: Shape defines a common interface for all shapes.
  2. Method Overriding: Each shape subclass provides its own implementation of calculateArea() and displayInfo().
  3. Polymorphic Container: A Shape array holds references to different shape objects.
  4. Dynamic Binding: The appropriate methods are called based on the actual object type at runtime.
  5. Code Reusability: The calculateTotalArea() method works with any Shape subclass, even those defined in the future.

This is a powerful example of how polymorphism allows us to write flexible, extensible code. The calculateTotalArea() method doesn't need to know anything about the specific shapes—it only needs to know they are all Shape objects that can calculate their area.



Interviewers ask about polymorphism for several important reasons:


  • It's one of the four fundamental OOP principles (along with encapsulation, inheritance, and abstraction)
  • It tests your understanding of Java's type system and method dispatch mechanisms
  • It evaluates your ability to design flexible, maintainable code
  • Many design patterns rely on polymorphism (Strategy, Factory Method, Template Method, etc.)

I've been asked about polymorphism in nearly every Java interview I've participated in, both as a candidate and as an interviewer. A strong answer shows not just theoretical knowledge but an understanding of how and when to apply polymorphism to solve real problems.



Watch Out!

Here are some common misunderstandings about polymorphism that interviewers often probe for:

  1. Confusing Overloading and Overriding: Remember that overloading (same method name, different parameters) is compile-time polymorphism, while overriding (same method in parent and child) enables runtime polymorphism.
  2. Access to Subclass-specific Methods: A parent class reference to a child class object cannot access methods specific to the child class without casting.
  3. Static Method Overriding: Static methods cannot be overridden in the true sense; they are hidden, not overridden (method hiding vs. method overriding).
  4. Final Methods: Methods declared as final cannot be overridden, which prevents runtime polymorphism for those methods.
  5. Private Methods: Private methods are not inherited and thus cannot be overridden, limiting polymorphic behavior.




Polymorphism is a powerful object-oriented programming concept that allows for flexibility, extensibility, and cleaner design in Java applications. By enabling objects of different types to be treated through a common interface, polymorphism reduces code duplication and makes your codebase more maintainable.


Understanding both compile-time polymorphism (method overloading) and runtime polymorphism (method overriding) is essential for Java developers. While method overloading provides convenience through multiple methods with the same name, it's runtime polymorphism that truly captures the essence of "one interface, many implementations."


Key takeaways:


  • Runtime polymorphism is achieved through inheritance and method overriding
  • Interfaces provide a powerful way to implement polymorphism in Java
  • Polymorphism enables code to work with objects at a higher level of abstraction
  • It's a fundamental concept for building flexible, maintainable Java applications
  • Many design patterns rely on polymorphic behavior

As you continue to develop in Java, you'll find that polymorphism becomes an intuitive part of how you design solutions. Recognizing when and how to leverage polymorphism is a mark of an experienced Java developer and can significantly improve the quality of your code.


About Techoral

Techoral provides high-quality tutorials, guides, and solutions for Java developers and tech enthusiasts.

Learn More
Subscribe to Our Newsletter

Get the latest updates and exclusive content delivered to your inbox!