Last Updated: February 12, 2026
Polymorphism allows the same method name or interface to exhibit different behaviors depending on the object that is invoking it.
The term "polymorphism" comes from Greek and means "many forms." In programming, it allows us to write code that is generic, extensible, and reusable, while the specific behavior is determined at runtime or compile-time based on the object’s actual type.
Polymorphism lets you call the same method on different objects, and have each object respond in its own way.
You write code that targets a common type, but the actual behavior is determined by the concrete implementation.
Think of a universal remote control.
powerOn(), volumeUp(), mute().For the user, the interface (remote) never changes. But internally, each device interprets the same signal differently.
That’s polymorphism in action. The same interface triggers different behaviors depending on the receiver (device type).
Here are four concrete benefits that polymorphism provides.
Polymorphism in OOP comes in two forms: compile-time (decided before the program runs) and runtime (decided while the program runs). Both allow the same method name to behave differently, but the mechanism is fundamentally different.
Compile-time polymorphism, also called method overloading, happens when you have multiple methods with the same name in the same class but with different parameter lists.
The compiler determines which version to call based on the number, types, or order of arguments at the call site. The decision is made before the program runs.
The compiler resolves which add() to call based on the arguments. Pass two ints, you get add(int, int). Pass two doubles, you get add(double, double). Pass three ints, you get add(int, int, int). No runtime decision needed.
Runtime polymorphism is the more powerful and more important form. It happens when a child class overrides a method defined in its parent class, and the decision of which version to call is made at runtime based on the actual type of the object, not the declared type of the reference.
Suppose you’re designing a system that sends notifications. You want to support email, SMS, push notifications, etc.
The key thing to notice: every element in the list is stored as a Notification reference, but the runtime calls the correct child class's send(). The variable type says Notification. The behavior says EmailNotification, SMSNotification, or PushNotification. That's runtime polymorphism.
Both interfaces and abstract classes enable polymorphism. In the notification example, you could define Notification as either an abstract class or an interface. The polymorphic behavior, calling send() on a base reference and having the child's version execute, works the same either way. So when should you use which?
Use an interface when the implementing classes are fundamentally different but share a capability. Email, Invoice, and Report have nothing in common structurally, but they can all send(). An interface defines that contract without forcing a shared hierarchy.
Use an abstract class when the implementing classes are a family with shared logic. All notifications need the same formatHeader() method, the same recipient and message fields, and the same constructor pattern. An abstract class provides all of that, plus the abstract send() that each child implements differently.
In practice, many designs use both. An abstract Notification class provides shared fields and formatting, while a Sendable interface marks anything that can be sent (notifications, reports, alerts).