Compile-time vs runtime polymorphism, how the compiler picks an overload, overriding rules (covariant returns, exceptions, access), dynamic method dispatch and @Override.
Published September 25, 2026
Polymorphism questions come in a predictable sequence: define it → overloading vs overriding → the rules → "what does this code print?". Being precise about the rules is what separates a good answer from an average one.
Short answer: Polymorphism ("many forms") means one interface or method name can behave differently depending on the actual object or the arguments. Java has two kinds:
abstract class Shape { abstract double area(); }
class Circle extends Shape { double r = 1; double area() { return Math.PI * r * r; } }
class Square extends Shape { double s = 2; double area() { return s * s; } }
List<Shape> shapes = List.of(new Circle(), new Square());
for (Shape s : shapes) System.out.println(s.area()); // same call, different behaviour per object
Key points to cover:
Shape works with shapes that don't exist yet (open/closed principle).Learn it in depth → Inheritance and Polymorphism
Short answer: Declaring several methods with the same name but different parameter lists in the same class (or inherited into it). The compiler decides which one to call from the arguments. That's compile-time polymorphism.
class PriceFormatter {
String format(long paise) { return format(paise, "INR"); }
String format(long paise, String currency) { return currency + " " + paise / 100.0; }
String format(BigDecimal amount) { return "INR " + amount.toPlainString(); }
}
Key points to cover:
println(int), println(String) and List.of(...).Short answer: It's the static form of polymorphism. One method name has many forms, and the right one is chosen at compile time from the declared types of the arguments.
Key points to cover:
void print(Object o) { System.out.println("object"); }
void print(String s) { System.out.println("string"); }
Object x = "hello";
print(x); // prints "object": the declared type is Object, so print(Object) is chosen
Short answer: It finds every method whose name matches and whose parameters can accept the arguments, then picks the most specific one. It does this in phases:
int → long), without boxing or varargs.void f(long x) { System.out.println("long"); }
void f(Integer x) { System.out.println("Integer"); }
void f(int... x) { System.out.println("varargs"); }
f(5); // "long": widening (phase 1) beats boxing (phase 2) and varargs (phase 3)
Common trap: if two candidates are equally specific, such as f(null) with f(String) and f(Integer), the call is ambiguous, and it's a compile error.
Short answer: No. int total() and long total() in the same class is a compile error. The compiler chooses an overload from the call's arguments, and a call like total(); gives it nothing to decide with.
Short answer: The method name must be the same, and the parameter lists must differ in the number, types or order of the parameters.
Key points to cover:
throws clause may differ, but they don't count towards telling overloads apart.void m(List<String>) and void m(List<Integer>) is a compile error.Short answer: A subclass provides its own implementation of an instance method inherited from a parent class or an interface, with the same name and parameter list. The implementation that runs is chosen at runtime, from the object's actual class.
class Notifier { void send(String msg) { System.out.println("log: " + msg); } }
class SmsNotifier extends Notifier {
@Override void send(String msg) { System.out.println("SMS: " + msg); }
}
Notifier n = new SmsNotifier();
n.send("OTP 4821"); // "SMS: OTP 4821": the runtime type decides
Learn it in depth → Inheritance and Polymorphism
Short answer:
public can't become protected).static, final and private methods can't be overridden.| Rule | Allowed | Not allowed |
|---|---|---|
| Return type | Animal get() → Dog get() (covariant) | Dog → Animal, or a primitive change |
| Access | protected → public | public → protected |
| Checked exceptions | Same, narrower, or none | New or broader (IOException → Exception) |
| Unchecked exceptions | Any | — |
final method | — | Can't be overridden |
static method | Can be hidden | Not overridden (no dynamic dispatch) |
private method | — | Not visible, so a same-name method is a new method |
Common trap: saying the return type must be "exactly the same". Covariant returns have been allowed since Java 5. clone() overrides commonly return the subclass type.
@Override annotation do?Short answer: It tells the compiler that you intend to override a method. If no matching method exists in a supertype, for example because of a typo or a wrong parameter type, compilation fails instead of silently creating a new overload.
class Money {
@Override
public boolean equals(Money other) { … } // compile error: this overloads equals(Object), it doesn't override it
}
Key points to cover:
HashSet would silently ignore that equals.Short answer: The JVM's mechanism for choosing which overridden method to run at runtime, based on the real class of the object rather than the reference type. It's how runtime polymorphism is implemented.
Key points to cover:
class A { String name = "A"; String who() { return "A"; } }
class B extends A { String name = "B"; @Override String who() { return "B"; } }
A obj = new B();
System.out.println(obj.who()); // "B": method dispatched at runtime
System.out.println(obj.name); // "A": fields are resolved by the declared type
Short answer: Each subclass has its own version, and a call through a parent reference runs the version belonging to the object's actual class. That's exactly what lets one loop handle Circle, Square and Triangle differently.
Key points to cover:
A → B → C), C inherits B's override unless it overrides the method again. The most specific override in the object's class hierarchy wins.Short answer: No. Constructors aren't inherited, so they can't be overridden, and they don't take part in dynamic dispatch. They can be overloaded, since a class can have several constructors with different parameters.
Common trap: calling an overridable method from a constructor. The subclass override runs before the subclass's fields are initialised:
class Base { Base() { init(); } void init() { } }
class Child extends Base {
private List<String> items = new ArrayList<>();
@Override void init() { items.add("x"); } // NPE: items is still null when Base() runs
}
Q: Overloading vs overriding in one line each? A: Overloading means the same name with different parameters in one class, resolved at compile time. Overriding means the same signature in a subclass that replaces inherited behaviour, resolved at runtime.
Q: Can we overload a static method, and can we override one? A: Overloading, yes. Overriding, no. A subclass static method with the same signature hides the parent's method, and the version called depends on the reference type used at compile time.
Q: Can an overriding method be synchronized or final when the parent's isn't?
A: Yes. synchronized, strictfp and final can be added in an override. Adding final stops further overriding down the hierarchy.
Q: Can an interface's default method be overridden?
A: Yes. The implementing class can override it like any other inherited method, and can call the original with InterfaceName.super.method().