Low-level design2 min

Polymorphism

Polymorphism comes from the Greek words poly (many) and morph (forms). In Object-Oriented Programming (OOP), it is the ability of a single interface or base class to represent different underlying forms (subclasses).

Simply put, polymorphism allows you to treat objects of different classes as if they were objects of the same class, provided they share a common interface or superclass.

Real-Life Analogy

Think of a smartphone. A smartphone has a generic action called communicate().

  • If you open the Phone app, communicate() makes a voice call.
  • If you open the SMS app, communicate() sends a text message.
  • If you open WhatsApp, communicate() sends a chat over the internet.

The action is the same (communicate), but the underlying implementation changes depending on the specific application taking the action.

Types of Polymorphism

  1. Compile-time Polymorphism (Static Binding / Method Overloading): Multiple methods in the same class have the same name but different parameters. (Note: True method overloading is not natively supported in JavaScript/TypeScript, but is common in Java/C++).
  2. Runtime Polymorphism (Dynamic Binding / Method Overriding): A subclass provides a specific implementation of a method that is already provided by its parent class.

Example of Runtime Polymorphism

typescript
abstract class Shape {
  abstract draw(): void;
}

class Circle extends Shape {
  draw(): void {
    console.log("Drawing a Circle 🔴");
  }
}

class Square extends Shape {
  draw(): void {
    console.log("Drawing a Square ⬛");
  }
}

class Triangle extends Shape {
  draw(): void {
    console.log("Drawing a Triangle 🔺");
  }
}

// The Power of Polymorphism:
// We can treat all of these different objects as a generic "Shape"
const shapes: Shape[] = [new Circle(), new Square(), new Triangle()];

// We don't need to check what type of shape it is; we just call draw().
// The correct method is resolved at runtime!
for (const shape of shapes) {
  shape.draw(); 
}

Why is it Important?

  • Flexibility and Extensibility: You can write code that operates on a superclass interface, and it will automatically work with any new subclasses you add in the future without modification. (This strongly supports the Open/Closed Principle).
  • Simplicity: It allows you to eliminate long if-else or switch statements that check object types. Instead of asking an object what it is, you simply tell it to perform an action, and it knows how to do it correctly.