Polymorphism


Polymorphism Interview with follow-up questions

1. What is polymorphism in Java?

Polymorphism ("many forms") is the ability for the same reference/operation to behave differently depending on the actual object type. It lets you treat objects of different classes through a common supertype, and the right implementation is selected automatically.

Animal a = new Dog();   // superclass reference, subclass object
a.sound();              // runs Dog's version

The two forms interviewers expect:

  • Compile-time (static) polymorphismmethod overloading, resolved by the compiler from argument types.
  • Runtime (dynamic) polymorphismmethod overriding, where a superclass/interface reference dispatches to the actual object's overridden method at runtime (Java methods are virtual by default unless static/final/private).

Why it matters: you write code against a general type (interface or base class) while concrete behavior varies — the foundation of extensible, loosely-coupled design (List ref backed by any implementation; a Comparator accepting any comparison).

A current note that signals up-to-date Java: alongside subtype polymorphism, modern Java uses pattern matching for switch over sealed types to branch on the concrete type exhaustively and safely — a complementary tool to overriding. Knowing both styles is what's expected now.

↑ Back to top

Follow-up 1

Can you provide an example of polymorphism?

Sure! Here's an example:

class Animal {
    public void makeSound() {
        System.out.println("Animal is making a sound");
    }
}

class Dog extends Animal {
    public void makeSound() {
        System.out.println("Dog is barking");
    }
}

class Cat extends Animal {
    public void makeSound() {
        System.out.println("Cat is meowing");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal animal1 = new Dog();
        Animal animal2 = new Cat();

        animal1.makeSound(); // Output: Dog is barking
        animal2.makeSound(); // Output: Cat is meowing
    }
}

In this example, the Animal class is the superclass, and the Dog and Cat classes are subclasses. The makeSound() method is overridden in each subclass, allowing the objects to behave differently when the method is called.

Follow-up 2

What are the types of polymorphism in Java?

There are two types of polymorphism in Java:

  1. Compile-time polymorphism (also known as method overloading): This occurs when there are multiple methods with the same name but different parameters in a class. The appropriate method is chosen at compile-time based on the arguments provided.

  2. Runtime polymorphism (also known as method overriding): This occurs when a subclass provides its own implementation of a method that is already defined in its superclass. The appropriate method is chosen at runtime based on the actual type of the object.

Follow-up 3

How is polymorphism implemented in Java?

Polymorphism in Java is implemented through method overriding and method overloading.

Method overriding is used to achieve runtime polymorphism. It allows a subclass to provide a different implementation of a method that is already defined in its superclass. To override a method, the subclass must have the same method signature (name and parameters) as the superclass method.

Method overloading is used to achieve compile-time polymorphism. It allows a class to have multiple methods with the same name but different parameters. The appropriate method is chosen at compile-time based on the arguments provided.

Follow-up 4

What is the advantage of polymorphism?

The advantage of polymorphism in Java is that it allows for code reusability and flexibility. By treating objects of different classes as objects of a common superclass, you can write code that is more generic and can work with a variety of objects. This makes the code easier to maintain and enhances its flexibility, as new subclasses can be added without modifying the existing code.

2. What is the difference between static and dynamic polymorphism?

The difference is when the method to call is decided:

  • Static (compile-time) polymorphism — resolved by the compiler using the declared/static types of the arguments. Achieved through method overloading. (Note: Java has no user-defined operator overloading, unlike C++ — only + for strings is built-in.)
  • Dynamic (runtime) polymorphism — resolved by the JVM at runtime based on the actual object type. Achieved through method overriding and virtual dispatch.
print(1);            // overloading — compiler picks print(int)   [static]
Animal a = new Dog();
a.sound();           // overriding — runtime picks Dog.sound()    [dynamic]
Static Dynamic
Bound at compile time runtime
Based on argument types (overloading) object type (overriding)
Mechanism overloading overriding
Performance slightly faster (early binding) virtual dispatch (negligible)

The clarification interviewers reward: in Java, instance methods are virtual by default (so overriding is dynamic), while static, final, and private methods use static binding (resolved at compile time, not overridable). Overloading is convenience/readability; overriding is the engine of extensible polymorphic design.

↑ Back to top

Follow-up 1

Can you provide an example of static and dynamic polymorphism?

Sure! Here's an example of static polymorphism:

#include 

class Shape {
public:
    void draw() {
        std::cout << "Drawing a shape" << std::endl;
    }
    void draw(int x) {
        std::cout << "Drawing a shape with size " << x << std::endl;
    }
};

int main() {
    Shape shape;
    shape.draw();
    shape.draw(10);
    return 0;
}

And here's an example of dynamic polymorphism in C++ using inheritance and virtual functions:

#include 

class Shape {
public:
    virtual void draw() {
        std::cout << "Drawing a shape" << std::endl;
    }
};

class Circle : public Shape {
public:
    void draw() override {
        std::cout << "Drawing a circle" << std::endl;
    }
};

int main() {
    Shape* shape = new Circle();
    shape->draw();
    delete shape;
    return 0;
}

Follow-up 2

When would you use static polymorphism over dynamic and vice versa?

Static polymorphism is useful when the behavior of a function can be determined at compile-time based on the static types of the arguments. It can lead to more efficient code execution as the function call is resolved at compile-time.

Dynamic polymorphism is useful when the behavior of a function needs to be determined at runtime based on the dynamic type of the object. It allows for more flexibility and extensibility in the code, as different derived classes can override the base class function to provide their own implementation.

Follow-up 3

How does Java handle dynamic method dispatch?

In Java, dynamic method dispatch is achieved through the use of virtual functions and method overriding. When a method is declared as virtual in the base class, it can be overridden by derived classes. The actual method to be called is determined at runtime based on the dynamic type of the object.

Here's an example in Java:

class Shape {
    public void draw() {
        System.out.println("Drawing a shape");
    }
}

class Circle extends Shape {
    @Override
    public void draw() {
        System.out.println("Drawing a circle");
    }
}

public class Main {
    public static void main(String[] args) {
        Shape shape = new Circle();
        shape.draw();
    }
}

3. What is method overloading and method overriding?

Two distinct OOP mechanisms that are easy to confuse:

  • Method overloading — multiple methods with the same name but different parameter lists in the same class. Resolved by the compiler from the argument types (static polymorphism). You can't overload on return type alone.
  • Method overriding — a subclass redefines an inherited method with the same signature to change its behavior. Resolved by the JVM at runtime based on the object's actual type (dynamic polymorphism).
// Overloading (same class, different params)
double area(double r)            { return Math.PI*r*r; }
double area(double w, double h)  { return w*h; }

// Overriding (subclass, same signature)
class Circle extends Shape { @Override double area() { ... } }

The crisp distinction interviewers want: overloading = same name, different parameters, chosen at compile time; overriding = same signature, chosen at runtime by the real type. Key constraints — overriding must keep a compatible signature (covariant return allowed, no broader checked exceptions, no narrower access), and static/final/private methods can be overloaded but not overridden. Always annotate overrides with @Override. Overloading improves API ergonomics; overriding powers polymorphism.

↑ Back to top

Follow-up 1

Can you provide an example of method overloading and method overriding?

Sure! Here's an example of method overloading:

public class Calculator {
    public int add(int a, int b) {
        return a + b;
    }

    public double add(double a, double b) {
        return a + b;
    }
}

In this example, the Calculator class has two add methods with the same name but different parameter types. The first add method takes two integers as parameters and returns an integer, while the second add method takes two doubles as parameters and returns a double.

And here's an example of method overriding:

public class Animal {
    public void makeSound() {
        System.out.println("The animal makes a sound.");
    }
}

public class Dog extends Animal {
    @Override
    public void makeSound() {
        System.out.println("The dog barks.");
    }
}

In this example, the Animal class has a makeSound method, and the Dog class overrides this method to provide a specific implementation for dogs.

Follow-up 2

What are the rules for method overloading and method overriding?

The rules for method overloading are as follows:

  1. The methods must have the same name.
  2. The methods must have different parameter types or a different number of parameters.
  3. The methods may have different return types, but it is not a determining factor for overloading.

The rules for method overriding are as follows:

  1. The method in the subclass must have the same name, return type, and parameters as the method in the superclass.
  2. The method in the subclass must be declared with the @Override annotation.
  3. The method in the subclass must have the same or a more accessible access modifier than the method in the superclass.
  4. The method in the subclass must not throw a checked exception that is not declared by the method in the superclass.

Follow-up 3

How do method overloading and method overriding support polymorphism?

Method overloading and method overriding both support polymorphism, which is the ability of an object to take on many forms. Polymorphism allows objects of different classes to be treated as objects of a common superclass.

Method overloading supports polymorphism by allowing different methods with the same name to be called based on the type and number of arguments passed to them. This allows for more flexibility and convenience when working with objects of different types.

Method overriding supports polymorphism by allowing a subclass to provide a specific implementation of a method that is already defined in its superclass. This allows for specialization and customization of behavior for different types of objects while still maintaining a common interface.

4. What is the role of the 'super' keyword in relation to polymorphism?

super refers to the immediate superclass, and it's important precisely because of polymorphism: when a subclass overrides a method, the subclass version normally takes over — super lets you still reach the parent's version.

Its three uses:

  • super.method() — call the superclass's (overridden) implementation, e.g. to extend rather than fully replace behavior.
  • super.field — access a hidden superclass field.
  • super(args) — invoke the superclass constructor (must be the first statement in the subclass constructor).
class Account { double fee() { return 5; } }
class Premium extends Account {
    @Override double fee() {
        return super.fee() * 0.5;   // build on the parent's behavior
    }
}

How it relates to polymorphism: dynamic dispatch always routes a normal call to the most-derived override, so without super a subclass couldn't reuse the base logic it's specializing. super.method() is a non-virtual call — it goes straight to the named superclass version, bypassing dynamic dispatch. The common pattern is an override that does some new work and delegates the rest with super (like calling super.toString() or chaining constructors). It keeps subclasses from duplicating superclass logic.

↑ Back to top

Follow-up 1

Can you provide an example where 'super' keyword is used?

Sure! Here's an example:

public class Animal {
    public void makeSound() {
        System.out.println("Animal is making a sound");
    }
}

public class Dog extends Animal {
    @Override
    public void makeSound() {
        super.makeSound();
        System.out.println("Dog is barking");
    }
}

public class Main {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.makeSound();
    }
}

In this example, the 'super' keyword is used in the makeSound() method of the Dog class to call the makeSound() method of the superclass Animal. This allows the Dog class to inherit and extend the behavior of the Animal class.

Follow-up 2

What happens if we do not use 'super' keyword?

If we do not use the 'super' keyword, the subclass will override or hide the method or variable of the superclass. This means that when the subclass is used, the overridden or hidden version of the method or variable will be used instead of the superclass version. This can lead to unexpected behavior and can prevent the subclass from accessing or utilizing the functionality provided by the superclass.

Follow-up 3

Can 'super' keyword be used with methods, variables, and constructors?

Yes, the 'super' keyword can be used with methods, variables, and constructors. When used with methods, it allows the subclass to call and invoke the superclass version of the method. When used with variables, it allows the subclass to access the superclass version of the variable. When used with constructors, it allows the subclass constructor to call and invoke the superclass constructor.

5. What is 'upcasting' and 'downcasting' in Java?

Both convert a reference between a subclass and superclass type in a hierarchy:

  • Upcasting — treating a subclass object as its superclass type. It's implicit and always safe (a Dog is an Animal):
Animal a = new Dog();   // upcast — no cast needed
a.eat();                // can call Animal's methods; Dog's overrides still run
  • Downcasting — treating a superclass reference as a subclass type. It requires an explicit cast and is only valid if the object really is that subtype — otherwise it throws ClassCastException at runtime:
Animal a = new Dog();
Dog d = (Dog) a;        // downcast — OK because a actually is a Dog
((Cat) a).meow();       // ClassCastException — a is not a Cat

Always guard a downcast with instanceof. The modern idiom interviewers want is instanceof pattern matching (Java 16+), which checks and casts in one step:

if (a instanceof Dog d) d.bark();   // safe: only binds d if a is a Dog

The framing: upcasting enables polymorphism (program against the supertype); downcasting recovers subtype-specific behavior and should be rare and guarded. A frequent need to downcast often signals a design that could use polymorphism or pattern-matching switch over a sealed type instead.

↑ Back to top

Follow-up 1

Can you provide an example of 'upcasting' and 'downcasting'?

Sure! Here's an example of 'upcasting' and 'downcasting' in Java:

// Upcasting
Animal animal = new Dog();

// Downcasting
Dog dog = (Dog) animal;

Follow-up 2

What is the use of 'upcasting' and 'downcasting'?

'Upcasting' is useful when we want to treat an object of a subclass as an object of its superclass. This allows us to access only the common methods and fields defined in the superclass, while hiding the specific implementation details of the subclass.

'Downcasting' is useful when we want to access the specific methods and fields of a subclass that are not present in the superclass. It allows us to take advantage of the additional functionality provided by the subclass.

Overall, 'upcasting' and 'downcasting' provide flexibility and polymorphism in Java.

Follow-up 3

What happens if downcasting is not done properly?

If downcasting is not done properly, it can result in a 'ClassCastException' at runtime. This occurs when we try to cast an object to a subclass type that is not compatible with its actual type.

For example, let's say we have a class hierarchy where 'Cat' is a subclass of 'Animal'. If we try to downcast an 'Animal' object to a 'Cat' object, but the actual object is not an instance of 'Cat', a 'ClassCastException' will be thrown.

To avoid this, it is recommended to use the 'instanceof' operator to check the compatibility of types before performing downcasting.

Live mock interview

Mock interview: Polymorphism

Intermediate ~5 min Your own free AI key

Your voice and your AI key never touch our servers; the key stays in this browser and is sent only to Google. Only your round scores are saved to track progress.