|

JavaScript 35 🧬 Polymorphism

Polymorphism means “many forms” — it’s the ability of different classes to respond to the same method call in their own way. It’s one of the four pillars of object-oriented programming (along with encapsulation, inheritance, and abstraction).


A Quick Look at the Example

class Animal {
    speak() {
        return "Some generic animal sound";
    }
}

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

class Cat extends Animal {
    speak() {
        return "Meow!";
    }
}

class Cow extends Animal {
    speak() {
        return "Moo!";
    }
}

// Same method call — different behavior
const animals = [new Dog(), new Cat(), new Cow()];

animals.forEach(animal => {
    console.log(animal.speak());
});
// "Woof!"
// "Meow!"
// "Moo!"

Key idea: animal.speak() looks identical for every animal, but produces a different result depending on the actual class of the object.


What is Polymorphism?

Polymorphism allows objects of different classes to be treated as objects of a common parent class, while each retains its own behavior.

Key Concepts

ConceptDescription
Same interfaceAll objects share the same method names
Different behaviorEach class implements the method differently
Runtime decisionWhich method runs is decided when the code runs
Inheritance-basedPolymorphism typically uses inheritance

Polymorphism Through Inheritance

The most common form of polymorphism in JavaScript is method overriding with inheritance.

class Shape {
    area() {
        return 0;
    }

    describe() {
        return `A shape with area ${this.area()}`;
    }
}

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;
    }
}

class Triangle extends Shape {
    constructor(base, height) {
        super();
        this.base = base;
        this.height = height;
    }
    area() {
        return (this.base * this.height) / 2;
    }
}

// Polymorphic behavior
const shapes = [
    new Circle(5),
    new Rectangle(4, 6),
    new Triangle(3, 8)
];

shapes.forEach(shape => {
    console.log(shape.describe());
});
// "A shape with area 78.53981633974483"
// "A shape with area 24"
// "A shape with area 12"

What’s happening:

  • shape.describe() calls this.area()
  • this.area() resolves to the actual class’s method
  • The same call produces different behavior

How Method Resolution Works

When you call a method on an object, JavaScript looks up the prototype chain:

shapes[0].area()
    │
    ├── Is area on the Circle instance?       → No
    ├── Is area on Circle.prototype?          → Yes! ✅ Use this
    └── (Stops here — doesn't check Shape)

Visual:

Circle instance
    │
    └── [[Prototype]] → Circle.prototype
                            ├── area()     ← Found here for Circle
                            │
                            └── [[Prototype]] → Shape.prototype
                                                    ├── area()  ← Base (overridden)
                                                    └── describe()

Polymorphism in Action — Real Examples

Example 1: Payment Methods

class PaymentMethod {
    pay(amount) {
        throw new Error("pay() must be implemented");
    }
}

class CreditCard extends PaymentMethod {
    constructor(number) {
        super();
        this.number = number;
    }
    pay(amount) {
        return `Paid $${amount} with credit card ending in ${this.number.slice(-4)}`;
    }
}

class PayPal extends PaymentMethod {
    constructor(email) {
        super();
        this.email = email;
    }
    pay(amount) {
        return `Paid $${amount} via PayPal (${this.email})`;
    }
}

class Crypto extends PaymentMethod {
    constructor(wallet) {
        super();
        this.wallet = wallet;
    }
    pay(amount) {
        return `Paid $${amount} in crypto to wallet ${this.wallet.slice(0, 8)}...`;
    }
}

// Polymorphic usage
function processPayment(paymentMethod, amount) {
    return paymentMethod.pay(amount);
}

const methods = [
    new CreditCard("4111-1111-1111-1234"),
    new PayPal("alice@example.com"),
    new Crypto("0xABC123DEF456")
];

methods.forEach(m => console.log(processPayment(m, 99.99)));
// "Paid $99.99 with credit card ending in 1234"
// "Paid $99.99 via PayPal (alice@example.com)"
// "Paid $99.99 in crypto to wallet 0xABC123..."

Why this is powerful:

  • processPayment() doesn’t need to know the exact class
  • It just calls .pay() on whatever object it receives
  • Adding a new payment method requires no changes to processPayment()

Example 2: Notification System

class Notification {
    send(message) {
        throw new Error("send() must be implemented");
    }
}

class EmailNotification extends Notification {
    constructor(email) {
        super();
        this.email = email;
    }
    send(message) {
        return `📧 Email to ${this.email}: ${message}`;
    }
}

class SMSNotification extends Notification {
    constructor(phone) {
        super();
        this.phone = phone;
    }
    send(message) {
        return `📱 SMS to ${this.phone}: ${message}`;
    }
}

class PushNotification extends Notification {
    constructor(deviceId) {
        super();
        this.deviceId = deviceId;
    }
    send(message) {
        return `🔔 Push to device ${this.deviceId}: ${message}`;
    }
}

// Polymorphic dispatch
function notifyAll(notifiers, message) {
    return notifiers.map(n => n.send(message));
}

const notifiers = [
    new EmailNotification("alice@example.com"),
    new SMSNotification("+1-555-1234"),
    new PushNotification("device-abc")
];

const results = notifyAll(notifiers, "Server restarted");
results.forEach(r => console.log(r));
// "📧 Email to alice@example.com: Server restarted"
// "📱 SMS to +1-555-1234: Server restarted"
// "🔔 Push to device device-abc: Server restarted"

Example 3: Game Characters

class Character {
    constructor(name, hp) {
        this.name = name;
        this.hp = hp;
    }

    attack() {
        return `${this.name} attacks for 10 damage.`;
    }

    takeDamage(amount) {
        this.hp -= amount;
        return `${this.name} takes ${amount} damage. HP: ${this.hp}`;
    }

    describe() {
        return `${this.name} (HP: ${this.hp})`;
    }
}

class Warrior extends Character {
    constructor(name) {
        super(name, 150);
        this.rage = 0;
    }
    attack() {
        this.rage += 10;
        return `${this.name} swings a sword for 15 damage! Rage: ${this.rage}`;
    }
}

class Mage extends Character {
    constructor(name) {
        super(name, 80);
        this.mana = 100;
    }
    attack() {
        if (this.mana >= 20) {
            this.mana -= 20;
            return `${this.name} casts fireball for 30 damage! Mana: ${this.mana}`;
        }
        return `${this.name} is out of mana!`;
    }
}

class Archer extends Character {
    constructor(name) {
        super(name, 100);
        this.arrows = 10;
    }
    attack() {
        if (this.arrows > 0) {
            this.arrows--;
            return `${this.name} shoots an arrow for 20 damage! Arrows: ${this.arrows}`;
        }
        return `${this.name} is out of arrows!`;
    }
}

// Polymorphic combat
const party = [
    new Warrior("Conan"),
    new Mage("Merlin"),
    new Archer("Robin")
];

function battle(characters) {
    return characters.map(c => c.attack());
}

console.log(battle(party));
// [
//   "Conan swings a sword for 15 damage! Rage: 10",
//   "Merlin casts fireball for 30 damage! Mana: 80",
//   "Robin shoots an arrow for 20 damage! Arrows: 9"
// ]

Example 4: File Readers

class FileReader {
    read() {
        throw new Error("read() must be implemented");
    }
}

class TextReader extends FileReader {
    constructor(path) {
        super();
        this.path = path;
    }
    read() {
        return `Reading text from ${this.path}...`;
    }
}

class JSONReader extends FileReader {
    constructor(path) {
        super();
        this.path = path;
    }
    read() {
        return `Parsing JSON from ${this.path}...`;
    }
}

class CSVReader extends FileReader {
    constructor(path) {
        super();
        this.path = path;
    }
    read() {
        return `Parsing CSV from ${this.path}...`;
    }
}

// Factory + polymorphism
function createReader(filePath) {
    if (filePath.endsWith('.txt')) return new TextReader(filePath);
    if (filePath.endsWith('.json')) return new JSONReader(filePath);
    if (filePath.endsWith('.csv')) return new CSVReader(filePath);
    throw new Error("Unsupported file type");
}

const files = ['notes.txt', 'data.json', 'report.csv'];

files.forEach(file => {
    const reader = createReader(file);
    console.log(reader.read());
});
// "Reading text from notes.txt..."
// "Parsing JSON from data.json..."
// "Parsing CSV from report.csv..."

Types of Polymorphism

TypeDescriptionExample
Method OverridingSubclass replaces parent methodDog.speak() overrides Animal.speak()
Method OverloadingSame name, different parametersNot directly supported in JS
Duck TypingIf it walks like a duck…Any object with .speak() works
ParametricGeneric functionsmap(), filter() work on any type

Duck Typing (Dynamic Polymorphism)

In JavaScript, polymorphism often works through duck typing — “if it walks like a duck and quacks like a duck, it’s a duck.”

// No inheritance needed!
class Robot {
    speak() {
        return "Beep boop!";
    }
}

class Human {
    speak() {
        return "Hello!";
    }
}

class Alien {
    speak() {
        return "Zorp glorp!";
    }
}

// Same function works with any object that has speak()
function makeItSpeak(thing) {
    return thing.speak();
}

const beings = [new Robot(), new Human(), new Alien()];
beings.forEach(b => console.log(makeItSpeak(b)));
// "Beep boop!"
// "Hello!"
// "Zorp glorp!"

Note: Robot, Human, and Alien don’t share a parent — but they all have a speak() method, so the function works with all of them.


Polymorphism vs Inheritance

AspectInheritancePolymorphism
PurposeReuse codeDifferent behavior via same interface
Relationship“is-a” (Dog is an Animal)“responds-to” (all can speak())
Mechanismextends keywordMethod overriding, duck typing
FocusCode sharingBehavior variation

Inheritance enables polymorphism, but polymorphism is about behavior, not just structure.


Complete Example

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Polymorphism</title>
    <style>
        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            max-width: 800px;
            margin: 0 auto;
            padding: 20px;
            background: #f8f9fa;
            color: #333;
            line-height: 1.6;
        }
        h1 { color: #007bff; border-bottom: 3px solid #007bff; padding-bottom: 10px; }
        h2 { color: #28a745; border-left: 4px solid #28a745; padding-left: 15px; margin-top: 30px; }
        .demo-box {
            background: white;
            padding: 20px;
            border-radius: 8px;
            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
            margin: 15px 0;
        }
        pre {
            background: #1e1e1e;
            color: #d4d4d4;
            padding: 15px;
            border-radius: 8px;
            overflow-x: auto;
            font-family: 'Courier New', monospace;
            font-size: 0.9rem;
            line-height: 1.8;
        }
        .keyword { color: #569cd6; }
        .string { color: #ce9178; }
        .number { color: #b5cea8; }
        .function { color: #dcdcaa; }
        .comment { color: #6a9955; }
        .boolean { color: #569cd6; }
        .class-name { color: #4ec9b0; }
        #output {
            background: #e9ecef;
            padding: 15px;
            border-radius: 8px;
            margin-top: 15px;
            min-height: 40px;
            font-family: 'Courier New', monospace;
            font-size: 0.85rem;
            border-left: 4px solid #007bff;
            white-space: pre-wrap;
        }
        table {
            width: 100%;
            border-collapse: collapse;
            margin: 15px 0;
        }
        th, td {
            padding: 10px;
            border: 1px solid #ddd;
            text-align: left;
        }
        th { background: #007bff; color: white; }
        tr:nth-child(even) { background: #f8f9fa; }
        .btn {
            padding: 10px 20px;
            background: #007bff;
            color: white;
            border: none;
            border-radius: 6px;
            font-size: 1em;
            font-weight: bold;
            cursor: pointer;
            margin: 5px;
            transition: all 0.3s;
        }
        .btn:hover {
            background: #0056b3;
            transform: translateY(-2px);
        }
        .btn-success { background: #28a745; }
        .btn-success:hover { background: #1e7e34; }
        .hierarchy {
            font-family: 'Courier New', monospace;
            font-size: 0.85rem;
            background: #1e1e1e;
            color: #d4d4d4;
            padding: 20px;
            border-radius: 8px;
            margin: 15px 0;
            white-space: pre;
            overflow-x: auto;
            line-height: 1.8;
        }
        .class-display {
            font-family: 'Courier New', monospace;
            font-size: 1.05em;
            background: #f8f9fa;
            padding: 15px;
            border-radius: 8px;
            margin: 10px 0;
            border-left: 4px solid #007bff;
            white-space: pre-wrap;
            line-height: 1.8;
        }
    </style>
</head>
<body>

    <h1>Polymorphism</h1>

    <div class="demo-box">
        <h2>1. Basic Polymorphism</h2>
        <pre>
<span class="keyword">class</span> <span class="class-name">Animal</span> {
    <span class="function">speak</span>() {
        <span class="keyword">return</span> <span class="string">"Some generic animal sound"</span>;
    }
}

<span class="keyword">class</span> <span class="class-name">Dog</span> <span class="keyword">extends</span> <span class="class-name">Animal</span> {
    <span class="function">speak</span>() { <span class="keyword">return</span> <span class="string">"Woof!"</span>; }
}

<span class="keyword">class</span> <span class="class-name">Cat</span> <span class="keyword">extends</span> <span class="class-name">Animal</span> {
    <span class="function">speak</span>() { <span class="keyword">return</span> <span class="string">"Meow!"</span>; }
}

<span class="keyword">const</span> animals = [<span class="keyword">new</span> <span class="class-name">Dog</span>(), <span class="keyword">new</span> <span class="class-name">Cat</span>()];
animals.<span class="function">forEach</span>(a => console.log(a.<span class="function">speak</span>()));
<span class="comment">// "Woof!"</span>
<span class="comment">// "Meow!"</span>
        </pre>
    </div>

    <div class="demo-box">
        <h2>2. Polymorphic Method Resolution</h2>
        <div class="hierarchy">
<span style="color: #4ec9b0;">Shape</span> (parent)
  ├── area()       ← base implementation (returns 0)
  └── describe()   ← calls this.area()
  │
  ├── <span style="color: #4ec9b0;">Circle</span> extends Shape
  │     └── area() → OVERRIDES with πr²
  │
  ├── <span style="color: #4ec9b0;">Rectangle</span> extends Shape
  │     └── area() → OVERRIDES with w×h
  │
  └── <span style="color: #4ec9b0;">Triangle</span> extends Shape
        └── area() → OVERRIDES with ½bh

<span style="color: #dcdcaa;">shape.describe()</span>
    │
    └── calls <span style="color: #dcdcaa;">this.area()</span>
            │
            └── resolves to the ACTUAL class's area()
</div>
    </div>

    <div class="demo-box">
        <h2>3. The Power of Polymorphism</h2>
        <pre>
<span class="comment">// Without polymorphism — need if/else for each type</span>
<span class="keyword">function</span> <span class="function">badDescribe</span>(shape) {
    <span class="keyword">if</span> (shape <span class="keyword">instanceof</span> <span class="class-name">Circle</span>) {
        <span class="keyword">return</span> <span class="string">`Circle area: ${Math.PI * shape.radius ** 2}`</span>;
    } <span class="keyword">else if</span> (shape <span class="keyword">instanceof</span> <span class="class-name">Rectangle</span>) {
        <span class="keyword">return</span> <span class="string">`Rectangle area: ${shape.w * shape.h}`</span>;
    } <span class="keyword">else if</span> (shape <span class="keyword">instanceof</span> <span class="class-name">Triangle</span>) {
        <span class="keyword">return</span> <span class="string">`Triangle area: ${shape.base * shape.height / 2}`</span>;
    }
    <span class="comment">// Adding a new shape requires editing this function!</span>
}

<span class="comment">// With polymorphism — no changes needed for new shapes</span>
<span class="keyword">function</span> <span class="function">goodDescribe</span>(shape) {
    <span class="keyword">return</span> shape.<span class="function">describe</span>();
}
        </pre>
    </div>

    <div class="demo-box">
        <h2>4. Types of Polymorphism</h2>
        <table>
            <tr>
                <th>Type</th>
                <th>Description</th>
                <th>In JavaScript</th>
            </tr>
            <tr>
                <td><strong>Method Overriding</strong></td>
                <td>Subclass replaces parent method</td>
                <td>✅ Supported</td>
            </tr>
            <tr>
                <td><strong>Method Overloading</strong></td>
                <td>Same name, different parameters</td>
                <td>⚠️ Not directly (use defaults/rest)</td>
            </tr>
            <tr>
                <td><strong>Duck Typing</strong></td>
                <td>"If it walks like a duck..."</td>
                <td>✅ Natural in JS</td>
            </tr>
            <tr>
                <td><strong>Parametric</strong></td>
                <td>Generic functions</td>
                <td>✅ Supported (no types)</td>
            </tr>
        </table>
    </div>

    <div class="demo-box">
        <h2>5. Interactive: Payment System</h2>
        <p>Add different payment methods and process a payment.</p>
        <div style="margin: 10px 0;">
            <button class="btn btn-success" onclick="addCreditCard()">Add Credit Card</button>
            <button class="btn btn-success" onclick="addPayPal()">Add PayPal</button>
            <button class="btn btn-success" onclick="addCrypto()">Add Crypto</button>
            <button class="btn" onclick="processAll()">Process $99.99</button>
            <button class="btn" onclick="clearPayments()">Clear</button>
        </div>
        <div class="class-display" id="paymentDisplay">No payment methods added yet</div>
    </div>

    <div class="demo-box">
        <h2>6. Interactive: Shape Area Calculator</h2>
        <div style="margin: 10px 0;">
            <button class="btn btn-success" onclick="addCircleShape()">Add Circle</button>
            <button class="btn btn-success" onclick="addRectangleShape()">Add Rectangle</button>
            <button class="btn btn-success" onclick="addTriangleShape()">Add Triangle</button>
            <button class="btn" onclick="showShapeAreas()">Show All Areas</button>
            <button class="btn" onclick="clearShapes()">Clear</button>
        </div>
        <div class="class-display" id="shapeDisplay">No shapes added yet</div>
    </div>

    <div class="demo-box">
        <h2>7. Live Output — All Examples</h2>
        <div id="output">Loading...</div>
    </div>

    <script>
        // ============================================
        // Polymorphism — Live Demo
        // ============================================

        let results = [];

        // 1. Basic animal polymorphism
        results.push('📌 Basic Polymorphism:\n');

        class Animal {
            speak() {
                return "Some generic animal sound";
            }
        }

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

        class Cat extends Animal {
            speak() { return "Meow!"; }
        }

        class Cow extends Animal {
            speak() { return "Moo!"; }
        }

        const animals = [new Dog(), new Cat(), new Cow()];
        animals.forEach(a => {
            results.push('  ' + a.constructor.name + '.speak() → "' + a.speak() + '"');
        });
        results.push('');

        // 2. Shape polymorphism
        results.push('📌 Shape Polymorphism:\n');

        class Shape {
            area() { return 0; }
            describe() {
                return `${this.constructor.name}: area = ${this.area().toFixed(2)}`;
            }
        }

        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;
            }
        }

        class Triangle extends Shape {
            constructor(base, height) {
                super();
                this.base = base;
                this.height = height;
            }
            area() {
                return (this.base * this.height) / 2;
            }
        }

        const shapes = [
            new Circle(5),
            new Rectangle(4, 6),
            new Triangle(3, 8)
        ];

        shapes.forEach(s => {
            results.push('  ' + s.describe());
        });
        results.push('');

        // 3. Polymorphic function
        results.push('📌 Polymorphic Function (Same Call, Different Results):\n');

        function printArea(shape) {
            return `Area: ${shape.area().toFixed(2)}`;
        }

        shapes.forEach(s => {
            results.push('  printArea(' + s.constructor.name + ') → "' + printArea(s) + '"');
        });
        results.push('');

        // 4. Payment methods (polymorphism)
        results.push('📌 Payment Methods (Polymorphism):\n');

        class PaymentMethod {
            pay(amount) {
                throw new Error("pay() must be implemented");
            }
        }

        class CreditCard extends PaymentMethod {
            constructor(number) {
                super();
                this.number = number;
            }
            pay(amount) {
                return `Paid $${amount} with credit card ending in ${this.number.slice(-4)}`;
            }
        }

        class PayPal extends PaymentMethod {
            constructor(email) {
                super();
                this.email = email;
            }
            pay(amount) {
                return `Paid $${amount} via PayPal (${this.email})`;
            }
        }

        class Crypto extends PaymentMethod {
            constructor(wallet) {
                super();
                this.wallet = wallet;
            }
            pay(amount) {
                return `Paid $${amount} in crypto to wallet ${this.wallet.slice(0, 10)}...`;
            }
        }

        function processPayment(method, amount) {
            return method.pay(amount);
        }

        const methods = [
            new CreditCard("4111-1111-1111-1234"),
            new PayPal("alice@example.com"),
            new Crypto("0xABC123DEF456789")
        ];

        methods.forEach(m => {
            results.push('  ' + processPayment(m, 99.99));
        });
        results.push('');

        // 5. Duck typing (polymorphism without inheritance)
        results.push('📌 Duck Typing (No Inheritance):\n');

        class Robot {
            speak() { return "Beep boop!"; }
        }

        class Human {
            speak() { return "Hello!"; }
        }

        class Alien {
            speak() { return "Zorp glorp!"; }
        }

        function makeItSpeak(thing) {
            return thing.speak();
        }

        const beings = [new Robot(), new Human(), new Alien()];
        beings.forEach(b => {
            results.push('  ' + b.constructor.name + '.speak() → "' + makeItSpeak(b) + '"');
        });
        results.push('');

        // 6. Polymorphism in combat
        results.push('📌 Polymorphic Combat:\n');

        class Character {
            constructor(name, hp) {
                this.name = name;
                this.hp = hp;
            }
            attack() {
                return `${this.name} attacks for 10 damage.`;
            }
        }

        class Warrior extends Character {
            constructor(name) {
                super(name, 150);
                this.rage = 0;
            }
            attack() {
                this.rage += 10;
                return `${this.name} swings sword for 15 damage! Rage: ${this.rage}`;
            }
        }

        class Mage extends Character {
            constructor(name) {
                super(name, 80);
                this.mana = 100;
            }
            attack() {
                if (this.mana >= 20) {
                    this.mana -= 20;
                    return `${this.name} casts fireball for 30 damage! Mana: ${this.mana}`;
                }
                return `${this.name} is out of mana!`;
            }
        }

        class Archer extends Character {
            constructor(name) {
                super(name, 100);
                this.arrows = 10;
            }
            attack() {
                if (this.arrows > 0) {
                    this.arrows--;
                    return `${this.name} shoots arrow for 20 damage! Arrows: ${this.arrows}`;
                }
                return `${this.name} is out of arrows!`;
            }
        }

        const party = [
            new Warrior("Conan"),
            new Mage("Merlin"),
            new Archer("Robin")
        ];

        party.forEach(c => {
            results.push('  ' + c.attack());
        });
        results.push('');

        // 7. Polymorphism with abstract-like base
        results.push('📌 Polymorphic File Readers:\n');

        class FileReader {
            read() {
                throw new Error("read() must be implemented");
            }
        }

        class TextReader extends FileReader {
            constructor(path) { super(); this.path = path; }
            read() { return `Reading text from ${this.path}...`; }
        }

        class JSONReader extends FileReader {
            constructor(path) { super(); this.path = path; }
            read() { return `Parsing JSON from ${this.path}...`; }
        }

        class CSVReader extends FileReader {
            constructor(path) { super(); this.path = path; }
            read() { return `Parsing CSV from ${this.path}...`; }
        }

        function createReader(filePath) {
            if (filePath.endsWith('.txt')) return new TextReader(filePath);
            if (filePath.endsWith('.json')) return new JSONReader(filePath);
            if (filePath.endsWith('.csv')) return new CSVReader(filePath);
            throw new Error("Unsupported file type");
        }

        const files = ['notes.txt', 'data.json', 'report.csv'];
        files.forEach(file => {
            const reader = createReader(file);
            results.push('  ' + reader.read());
        });
        results.push('');

        // 8. Polymorphism in reduce
        results.push('📌 Polymorphism with Array Methods:\n');

        const totalArea = shapes.reduce((sum, shape) => {
            return sum + shape.area();
        }, 0);

        results.push('  Total area of all shapes: ' + totalArea.toFixed(2));
        results.push('  → reduce() calls area() polymorphically on each shape');

        document.getElementById('output').textContent = results.join('\n');

        // ============================================
        // Interactive: Payment System
        // ============================================

        // Reuse CreditCard, PayPal, Crypto classes from above
        const interactivePayments = [];

        function addCreditCard() {
            const n = "4111-1111-1111-" + Math.floor(1000 + Math.random() * 9000);
            interactivePayments.push(new CreditCard(n));
            updatePaymentDisplay('Added Credit Card');
        }

        function addPayPal() {
            const emails = ['alice@example.com', 'bob@test.com', 'carol@mail.com'];
            const email = emails[Math.floor(Math.random() * emails.length)];
            interactivePayments.push(new PayPal(email));
            updatePaymentDisplay('Added PayPal');
        }

        function addCrypto() {
            const wallet = "0x" + Math.random().toString(16).slice(2, 12).toUpperCase();
            interactivePayments.push(new Crypto(wallet));
            updatePaymentDisplay('Added Crypto');
        }

        function updatePaymentDisplay(message) {
            const display = document.getElementById('paymentDisplay');
            let text = message + '\n';
            text += '─────────────────────\n';
            text += `Total methods: ${interactivePayments.length}\n\n`;
            interactivePayments.forEach((m, i) => {
                text += `${i + 1}. ${m.constructor.name}\n`;
            });
            display.textContent = text;
        }

        function processAll() {
            const display = document.getElementById('paymentDisplay');

            if (interactivePayments.length === 0) {
                display.textContent = 'Add some payment methods first!';
                return;
            }

            let text = '💰 Processing $99.99 with all methods:\n';
            text += '─────────────────────\n';
            interactivePayments.forEach(m => {
                text += processPayment(m, 99.99) + '\n';
            });
            display.textContent = text;
        }

        function clearPayments() {
            interactivePayments.length = 0;
            document.getElementById('paymentDisplay').textContent = 'No payment methods added yet';
        }

        // ============================================
        // Interactive: Shape Area Calculator
        // ============================================

        const interactiveShapes = [];

        function addCircleShape() {
            const r = Math.floor(Math.random() * 8) + 2;
            interactiveShapes.push(new Circle(r));
            updateShapeDisplay(`Added Circle with radius ${r}`);
        }

        function addRectangleShape() {
            const w = Math.floor(Math.random() * 8) + 2;
            const h = Math.floor(Math.random() * 8) + 2;
            interactiveShapes.push(new Rectangle(w, h));
            updateShapeDisplay(`Added Rectangle ${w}×${h}`);
        }

        function addTriangleShape() {
            const b = Math.floor(Math.random() * 8) + 2;
            const h = Math.floor(Math.random() * 8) + 2;
            interactiveShapes.push(new Triangle(b, h));
            updateShapeDisplay(`Added Triangle base ${b}, height ${h}`);
        }

        function updateShapeDisplay(message) {
            const display = document.getElementById('shapeDisplay');
            let text = message + '\n';
            text += '─────────────────────\n';
            text += `Total shapes: ${interactiveShapes.length}\n\n`;
            interactiveShapes.forEach((s, i) => {
                text += `${i + 1}. ${s.describe()}\n`;
            });
            display.textContent = text;
        }

        function showShapeAreas() {
            const display = document.getElementById('shapeDisplay');

            if (interactiveShapes.length === 0) {
                display.textContent = 'Add some shapes first!';
                return;
            }

            let text = '📐 All Shape Areas (polymorphic):\n';
            text += '─────────────────────\n';
            let total = 0;
            interactiveShapes.forEach(s => {
                text += `${s.describe()}\n`;
                total += s.area();
            });
            text += '─────────────────────\n';
            text += `Total area: ${total.toFixed(2)}`;
            display.textContent = text;
        }

        function clearShapes() {
            interactiveShapes.length = 0;
            document.getElementById('shapeDisplay').textContent = 'No shapes added yet';
        }
    </script>

</body>
</html>

Quick Reference

What is Polymorphism?

AspectDescription
DefinitionMany forms — same interface, different behavior
MechanismMethod overriding (via inheritance) or duck typing
WhenMethod resolved at runtime
BenefitAdd new types without changing existing code

Types of Polymorphism

TypeDescriptionJavaScript
Method OverridingSubclass replaces parent method✅ Supported
Method OverloadingSame name, different params⚠️ Partial (defaults/rest)
Duck TypingAny object with matching method works✅ Natural
ParametricGeneric functions✅ Supported

Polymorphism vs Inheritance

AspectInheritancePolymorphism
Relationship“is-a”“responds-to”
MechanismextendsMethod overriding
FocusCode reuseBehavior variation
EnablesPolymorphism

Best Practices

Do This:

// Program to an interface, not an implementation
function render(shape) {
    return shape.draw(); // Works for any shape
}

// Use polymorphism to eliminate if/else chains
shapes.forEach(s => s.area()); // Not: if (s instanceof Circle) ...

// Provide base class with common methods
class Shape {
    area() { return 0; } // Default
    describe() { return `${this.area()}`; }
}

// Throw meaningful errors for unimplemented methods
class Abstract {
    method() {
        throw new Error("method() must be implemented");
    }
}

// Use duck typing for flexibility
function log(thing) {
    if (typeof thing.toString === 'function') {
        console.log(thing.toString());
    }
}

Don’t Do This:

// Don't use instanceof chains
if (animal instanceof Dog) {
    animal.woof();
} else if (animal instanceof Cat) {
    animal.meow();
}
// Better: animal.speak()

// Don't duplicate logic across classes
class Dog { speak() { return "Woof"; } }
class Cat { speak() { return "Meow"; } }
// Missing shared base class

// Don't make methods behave differently by type
class Shape {
    area() {
        if (this instanceof Circle) return Math.PI * this.r ** 2;
        if (this instanceof Rectangle) return this.w * this.h;
    }
}
// Better: override in each subclass

// Don't forget to call super in overriding methods
class Dog extends Animal {
    speak() {
        // Missing super.speak() if you wanted to extend
        return "Woof";
    }
}

Common Pitfalls

PitfallProblemSolution
instanceof chainsHard to maintainUse polymorphism
Missing base methodErrors at runtimeDefine abstract methods
Forgetting superParent logic lostCall super.method() if extending
Inconsistent signaturesConfusing APIKeep same method names/params
Over-engineeringToo many classesUse duck typing when simple

Real-World Use Cases

Use CaseWhy Polymorphism Helps
Payment systemsAdd new payment methods without changing checkout
UI componentsDifferent widgets respond to render()
Game charactersDifferent classes have different attack() behavior
File parsersSame read() interface for different formats
NotificationsEmail, SMS, Push all respond to send()
Database adaptersMySQL, PostgreSQL, MongoDB all respond to query()
Shape drawingCircle, Rectangle, Triangle all respond to draw()

Polymorphism in Design Patterns

PatternHow Polymorphism Is Used
StrategyDifferent algorithms behind same interface
FactoryCreates objects with polymorphic behavior
ObserverDifferent observers respond to update()
CommandDifferent commands respond to execute()
Template MethodSubclasses override steps of an algorithm
IteratorDifferent collections respond to next()

Pro Tip: Polymorphism is what makes object-oriented code flexible and extensible. The key insight is:

“Program to an interface, not an implementation.”

Instead of asking “what type is this?” and branching with if/else, you just call the method and let the object decide how to respond. This means:

  • Adding a new type requires no changes to existing code
  • Your functions become shorter and cleaner
  • Your design is open for extension, closed for modification

Remember: Polymorphism works best when all classes share a common interface (method names). Whether through inheritance or duck typing, the goal is the same — one call, many behaviors.


Stop using slow, ad-bloated tool sites! 🤮

🔎 Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)

⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free

🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!