Polymorphism (Greek for 'many forms') means the same method call or interface can behave differently depending on which actual object it's called on. It lets you write code against a general type and have it automatically work correctly with any specific subtype, without needing to know exactly which one it is at compile time.
class Shape {
function area(): number { return 0 }
}
class Circle extends Shape {
function __construct(radius) { this.radius = radius }
function area(): number { return 3.14159 * this.radius ** 2 }
}
class Rectangle extends Shape {
function __construct(width, height) { this.width = width; this.height = height }
function area(): number { return this.width * this.height }
}
function printArea(Shape shape) {
print("Area: " + shape.area()) // doesn't know or care which subclass this is
}
shapes = [new Circle(5), new Rectangle(4, 6)]
for shape in shapes:
printArea(shape) // calls the correct area() for each actual object
The printArea() function is written once, against the general Shape type, but produces correct, different results for a Circle and a Rectangle — this is polymorphism in action. At runtime, the program figures out which specific area() implementation to call based on the object's actual type, a mechanism called dynamic dispatch.
Why this matters: polymorphism is what lets you add a brand-new Triangle subclass later, and have every existing piece of code that works with Shape objects (like printArea) automatically work with Triangle too, with zero changes to that existing code. This is a cornerstone of extensible, maintainable software design.