Skip to content

Polymorphism

Polymorphism (from Greek: “poly” = many, “morph” = form) is the ability of objects of different types to respond to the same method call in different ways. It’s one of the four pillars of OOP and enables flexible, extensible code.

  • To write code that works with multiple types without knowing their specific implementation
  • To enable extensibility — new types can be added without changing existing code
  • To implement strategy pattern — swapping behaviors at runtime
  • To realize the principle “program to an interface, not an implementation”

A universal remote has buttons like “Power”, “Volume Up”, “Volume Down”:

  • When pointed at a TV, pressing “Power” turns the TV on/off
  • When pointed at a Sound System, pressing “Power” turns the audio on/off
  • When pointed at a DVD Player, pressing “Power” ejects the tray

Same button (interface), different behavior depending on the device (implementation). That’s polymorphism!


graph TD
Poly["Polymorphism"]
Poly --> Compile["Compile-Time (Static)<br/>Method Overloading"]
Poly --> Runtime["Run-Time (Dynamic)<br/>Method Overriding"]
Compile --> C1["Same name, different params<br/>Resolved at compile time"]
Runtime --> R1["Child overrides parent method<br/>Resolved at runtime"]
style Compile fill:#2196F3,color:#fff
style Runtime fill:#4CAF50,color:#fff

Without polymorphism:

// ❌ Without polymorphism — conditional logic for each type
function processPayment(payment, amount) {
if (payment.type === "credit") {
// Credit card processing
} else if (payment.type === "paypal") {
// PayPal processing
} else if (payment.type === "bitcoin") {
// Bitcoin processing
}
// Every time a new type is added, this function must change!
}

With polymorphism:

// ✅ With polymorphism — each type handles itself
function processPayment(payment, amount) {
payment.process(amount); // Each payment type implements its own process()
}
// New payment types can be added without changing processPayment()

Full details →

Multiple methods with the same name but different parameters. Resolved at compile time.

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

Full details →

A child class provides its own implementation of a parent method. Resolved at runtime.

class Animal { speak() { return "sound"; } }
class Dog extends Animal { speak() { return "Woof!"; } }

// Polymorphism through inheritance
class Shape {
area() { return 0; }
}
class Circle extends Shape {
constructor(radius) { super(); this.radius = radius; }
area() { return Math.PI * this.radius ** 2; }
}
class Rectangle extends Shape {
constructor(w, h) { super(); this.w = w; this.h = h; }
area() { return this.w * this.h; }
}
// Polymorphic function — works with any Shape
function printArea(shape) {
console.log(`Area: ${shape.area()}`);
}
const shapes = [new Circle(5), new Rectangle(4, 6)];
shapes.forEach(s => printArea(s));
// Area: 78.539...
// Area: 24

BenefitDescription
ExtensibilityAdd new types without changing existing code
MaintainabilityCentralized behavior in each class
ReusabilityGeneric functions work with many types
FlexibilitySwap implementations at runtime
Clean CodeNo switch/if-else chains for type checking

ConceptKey Point
PolymorphismSame interface, different implementations
OverloadingSame name, different parameters (compile-time)
OverridingChild redefines parent method (run-time)
BenefitsExtensibility, flexibility, clean code
Rule of ThumbCode to interfaces, not implementations

Previous Topic: Inheritance → Next Topic: Object Relationships → Related Topics: Method Overloading, Method Overriding