Polymorphism
Polymorphism (meaning 'many forms') is the ability of different types to respond to the same interface or method call in their own way, allowing code to work with objects of different types uniformly without knowing their specific type.
Explanation
Polymorphism is one of OOP's most powerful concepts. It means you can write code that calls area() on any Shape object — Circle, Rectangle, Triangle — and each implements area() differently. The calling code doesn't need to know which shape it has; it just calls area() and gets the right result. In JavaScript, polymorphism is achieved primarily through subtype polymorphism (method overriding via inheritance) and duck typing (if it has the methods you need, it works — regardless of its type). JavaScript is dynamically typed, so you don't need interfaces or abstract classes to achieve polymorphism — any object with the right method name works. Runtime dispatch: when you call shape.area(), JavaScript looks up the area method at runtime on the actual object. If shape is a Circle, it finds Circle.prototype.area. If it's a Rectangle, it finds Rectangle.prototype.area. The caller doesn't need a switch statement for each type — the runtime routes the call correctly. This is called dynamic dispatch. Duck typing: "if it walks like a duck and quacks like a duck, it's a duck." JavaScript doesn't check types — if your argument has a .render() method, you can call .render() on it regardless of its constructor. This makes polymorphism pervasive in JavaScript even without formal inheritance hierarchies. Array.prototype.map accepts any callback function regardless of its type; the DOM's EventTarget interface is implemented by all DOM nodes; React works with any object that implements the JSX interface.
Code Example
javascript// Subtype polymorphism through method overriding
class Shape {
area() { throw new Error('area() must be implemented by subclass'); }
toString() { return `${this.constructor.name}: area=${this.area().toFixed(2)}`; }
}
class Circle extends Shape {
constructor(r) { super(); this.r = r; }
area() { return Math.PI * this.r ** 2; }
}
class Rectangle extends Shape {
constructor(w, h) { super(); this.w = w; this.h = h; }
area() { return this.w * this.h; }
}
class Triangle extends Shape {
constructor(b, h) { super(); this.b = b; this.h = h; }
area() { return 0.5 * this.b * this.h; }
}
// Polymorphic: same code works for all shapes
const shapes = [new Circle(5), new Rectangle(4, 6), new Triangle(3, 8)];
const totalArea = shapes.reduce((sum, s) => sum + s.area(), 0);
shapes.forEach(s => console.log(s.toString()));
// Duck typing: no class needed
const fakeDuck = { quack() { return 'quack'; }, swim() { return 'splash'; } };
function makeNoise(duck) { return duck.quack(); } // works with any obj
console.log(makeNoise(fakeDuck)); // 'quack'
Why It Matters for Engineers
Polymorphism eliminates type-checking switch statements: instead of if (shape.type === 'circle') { ... } else if (shape.type === 'rectangle') { ... }, you call shape.area() and let the object handle it. Adding a new shape type only requires creating a new class — no modification to the calling code. This is the Open/Closed Principle: open for extension, closed for modification. Understanding polymorphism also explains why React's component model is so powerful: any component that accepts the same props interface is interchangeable. A Button component and an IconButton component can both accept an onClick prop; code that renders either doesn't need to know which one it's working with.
Related Terms
Inheritance · Class · Interface · Abstraction
Learn This In Practice
Go deeper with the full module on Beyond Vibe Code.
Programming Foundations → →