Skip to content

Polymorphism Interview Questions & Answers

15 questions Updated 2026-06-19 Share:

Java polymorphism interview questions — overloading vs overriding, runtime vs compile-time polymorphism, dynamic dispatch, overriding rules, covariant returns, method hiding, static/dynamic binding and programming to an interface.

Read the in-depth guideJava Polymorphism Explained — Overriding vs Overloading & Dynamic Dispatch(opens in new tab)
15 of 15

Polymorphism = "many forms." Java has two kinds:

  • Runtime (dynamic) polymorphism — method overriding. The JVM picks the method based on the actual object at runtime (dynamic dispatch).
  • Compile-time (static) polymorphism — method overloading. The compiler picks the method based on the declared argument types.
Animal a = new Dog();
a.sound();   // runtime: Dog's override is chosen

print(5);    // compile-time: print(int)
print("hi"); // compile-time: print(String)

"Polymorphism" in interviews usually means the runtime kind — the engine behind List<Shape> calling each shape's own area().

Overloading Overriding
What Same name, different parameters Subclass replaces a superclass method
Signature Must differ (type/count) Must be identical
Bound Compile time (static) Run time (dynamic)
Return type Can differ Same or covariant
class Calc {
  int add(int a, int b) { return a + b; }          // overload 1
  double add(double a, double b) { return a + b; }  // overload 2
}
class Base { void greet() { } }
class Sub extends Base { @Override void greet() { } } // override

Overloading is about offering variations of an operation; overriding is about specializing inherited behavior. Use @Override so the compiler verifies you actually overrode (and didn't accidentally overload).

To override (not overload), the subclass method must:

  1. Have the same name and parameter list (signature).
  2. Have the same or a covariant (narrower) return type.
  3. Have the same or wider access (you can't make it more restrictive).
  4. Throw the same or fewer/narrower checked exceptions.
class Base {
  protected Number make() throws IOException { return 1; }
}
class Sub extends Base {
  @Override public Integer make() { return 2; } // wider access, covariant, no checked throws — OK
}

private, static, and final methods can't be overridden. Rule of thumb: always add @Override — the compiler then rejects anything that's secretly an overload or a typo'd signature.

Dynamic (virtual) dispatch is how the JVM decides, at runtime, which overridden method to call — based on the actual object's class, not the reference type. It's the mechanism that makes runtime polymorphism work.

class Shape { double area() { return 0; } }
class Circle extends Shape { double area() { return Math.PI; } }

Shape s = new Circle();
s.area();    // Circle.area() — chosen by the object, not the Shape reference

Each object carries a pointer to its class's method table (vtable); the call looks up the override there. Rule of thumb: instance methods are virtual by default in Java — the runtime type wins.

  • Compile-time (static) polymorphism = overloading. The compiler resolves the call from the declared argument types; fixed before the program runs.
  • Runtime (dynamic) polymorphism = overriding. The JVM resolves the call from the actual object during execution.
void print(int x) { }
void print(Object o) { }
print(5);            // compile-time: print(int) chosen by arg type

Animal a = pickAnimal();
a.sound();           // runtime: depends on what pickAnimal() returned

Rule of thumb: overloading is decided by what the compiler sees (reference types); overriding by what actually exists (object type) at runtime.

Static binding is resolved by the compiler from the declared type — used for static, private, final, and overloaded methods, plus fields. Dynamic binding is resolved at runtime from the actual object type — used for overridden instance methods (virtual dispatch).

class A { static void s() { } void v() { } int f = 1; }
class B extends A { static void s() { } @Override void v() { } int f = 2; }

A x = new B();
x.v();   // B.v()  — dynamic binding (overridden)
x.s();   // A.s()  — static binding (hidden, by declared type)
x.f;     // 1      — fields are static-bound, not polymorphic

Key insight: fields and static methods are hidden, not overridden — they bind to the declared type, which is a classic trick question.

When a subclass declares a static method with the same signature as a parent static method, it hides rather than overrides it. The version called depends on the declared (compile-time) type, not the runtime object — the opposite of overriding.

class A { static String who() { return "A"; } }
class B extends A { static String who() { return "B"; } }

A ref = new B();
ref.who();   // "A"  — static method, resolved by declared type (hiding)
// If who() were an instance method, this would be "B" (overriding)

Because of this confusion, call static methods on the class (A.who()), never through an instance reference.

When overriding, the subclass method may return a subtype of the superclass method's return type — a covariant return. It lets overrides return more specific types without a cast.

class Animal { Animal reproduce() { return new Animal(); } }
class Dog extends Animal {
  @Override Dog reproduce() { return new Dog(); } // narrower return — legal
}
Dog puppy = new Dog().reproduce(); // no cast needed

Common in builder patterns and clone() overrides. (Parameters, by contrast, are not covariant — changing a parameter type makes it an overload, not an override.)

No to all three, for different reasons:

  • static — bound to the class, not the instance; redeclaring it in a subclass hides it (resolved by declared type), it isn't overridden.
  • final — explicitly sealed against overriding; won't compile if you try.
  • private — not visible to the subclass at all, so a same-named method is a brand-new, unrelated method.
class A {
  static void s() { }
  final void f() { }
  private void p() { }
}
class B extends A {
  static void s() { }    // hides, not overrides
  // void f() { }        // ERROR — can't override final
  void p() { }           // new method — A.p() is invisible here
}

Rule of thumb: only inherited, visible, non-final instance methods are truly overridable.

The compiler picks the most specific applicable overload using the compile-time (declared) argument types, in phases: exact/widening match first, then autoboxing, then varargs — preferring the path that needs the least conversion.

void m(Object o) { }
void m(Integer i) { }
void m(int... xs) { }

m(5);          // m(int...)? No — m(Integer) via boxing beats varargs?
               // Actually: widening/boxing phase -> m(Integer) chosen over varargs
Integer n = null;
m(n);          // m(Integer) — reference match, no boxing

Because it uses static types, m((Object) myInteger) calls m(Object) even if the value is an Integer. Rule of thumb: overloads are resolved by the reference type you pass, not the runtime value — avoid ambiguous overloads.

By declaring variables and parameters as the interface/supertype, your code works with any implementation — the runtime calls the actual object's methods. This decouples callers from concrete classes.

void process(List<String> items) { }   // accepts ArrayList, LinkedList, List.of...
List<String> list = new ArrayList<>();  // swap impl freely
// list = new LinkedList<>();           // no caller changes needed

"Program to an interface, not an implementation" — depend on List, Map, Collection, not ArrayList/HashMap. It makes code testable (inject fakes) and flexible (change implementations without ripple effects).

Use super.method(). Inside an override, super skips the current class's version and invokes the immediate superclass's implementation — useful to extend rather than replace behavior.

class Logger { void log(String m) { System.out.println(m); } }
class TimestampLogger extends Logger {
  @Override void log(String m) {
    super.log(java.time.Instant.now() + " " + m);  // reuse + augment
  }
}

You can only reach one level up — there's no super.super. Rule of thumb: super.method() is the "decorate the inherited behavior" tool.

An abstract method declares a contract with no body; each concrete subclass must provide an implementation. Code calling the method through the abstract type gets each subclass's behavior via dynamic dispatch — polymorphism with a compiler-enforced contract.

abstract class Shape { abstract double area(); }
class Circle extends Shape { double area() { return Math.PI * r * r; } double r = 1; }
class Square extends Shape { double area() { return s * s; } double s = 2; }

for (Shape sh : List.of(new Circle(), new Square()))
  sh.area();   // each computes its own — caller doesn't care which

Rule of thumb: abstract methods are "polymorphism you can't forget to implement" — the compiler stops you from leaving a hole.

During construction the subclass override runs before the subclass's fields are initialized, because the superclass constructor executes first. The override then sees default (null/0) values.

class Base {
  Base() { init(); }            // calls the override...
  void init() { }
}
class Sub extends Base {
  String name = "set";
  @Override void init() { System.out.println(name); } // prints null!
}
new Sub();   // null — name not assigned yet

This is dynamic dispatch biting you mid-construction. Rule of thumb: never call overridable (non-final, non-private) methods from a constructor — make such helpers final or private.

  • Extensibility — add a new subtype without touching code that uses the supertype (open/closed principle).
  • Decoupling — callers depend on an abstraction, so implementations swap freely (and fakes/mocks slot in for tests).
  • Cleaner code — replaces if/else/switch on type with one virtual call.
// No type-switching — each shape knows its own area
double total = 0;
for (Shape s : shapes) total += s.area();

Rule of thumb: when you see a switch on an object's "kind," polymorphism usually expresses it better — give each kind its own method.

More ways to practice

The self-quiz is live. Get notified when mock interviews and new question packs drop.

or
Join our WhatsApp Channel