March 17, 2026 6 minutes minutes read Admin

Polymorphic Dispatch - OOP Essentials

The Problem: Rigid Code

Imagine you have different kinds of things that behave similarly, but not exactly the same. Without polymorphic dispatch, you end up with long if/else or switch that grows each time you add a new type.

// fragile, every new animal means editing this function
function makeSound(animalType) {
  if (animalType === 'dog') console.log('woof');
  else if (animalType === 'cat') console.log('meow');
  // ... add another 'if' for each animal
}

You don't need to know OOP theory - just notice the pain.

The Solution: A Promise (Contract)

We want each object to own its behaviour. Polymorphism means “many forms” - we just need a common guarantee: every animal knows how to speak.

In many languages we use an interface or a base class. But in dynamic languages (JS, Python) we just agree on method names. Let's see it in JavaScript (works everywhere).

// a “contract” - if you have a .speak() method, you're an animal
const dog = {
  speak: () => console.log('woof')
};

const cat = {
  speak: () => console.log('meow')
};

Both objects dispatch polymorphically: the caller doesn't check type, it just calls .speak().

Polymorphic Call = No Conditionals

Write one function that works for any object that follows the contract.

function performSpeak(animal) {
  animal.speak();   // dispatch - dynamic decision
}

performSpeak(dog);  // woof
performSpeak(cat);  // meow

// add a new animal without touching existing code
const bird = {
  speak: () => console.log('chirp')
};
performSpeak(bird); // chirp

This is the heart of polymorphic dispatch: same call, different behaviour.

Classes (Optional but Clean)

If you work in a large codebase, classes help create many objects with the same interface. Still zero if/else.

class Animal {
  constructor(name) { this.name = name; }
  speak() { throw new Error('subclass must implement'); } // "abstract"
}

class Dog extends Animal {
  speak() { console.log(`${this.name} says woof`); }
}

class Cat extends Animal {
  speak() { console.log(`${this.name} says meow`); }
}

const animals = [new Dog('Bella'), new Cat('Luna')];
animals.forEach(a => a.speak()); // Bella says woof / Luna says meow

No type checks, no switches - adding new Cow doesn't break anything.

Production-ready: Payment Processors

E-commerce: different payment gateways. Polymorphism keeps it rock solid.

class PayPalProcessor {
  process(amount) {
    // real API logic here
    return `PayPal: charged $${amount}`;
  }
}

class StripeProcessor {
  process(amount) {
    return `Stripe: charged $${amount} (fee 2.9%)`;
  }
}

class CashProcessor {
  process(amount) {
    return `Cash: collect $${amount} (no fee)`;
  }
}

// checkout function - completely open to new processors
function checkout(paymentMethod, amount) {
  console.log(paymentMethod.process(amount));
}

// usage - no matter what processor you add later
checkout(new PayPalProcessor(), 99);
checkout(new StripeProcessor(), 45);
checkout(new CashProcessor(), 20);

New CryptoProcessor? just pass it. Zero changes in checkout.

This pattern is used in frameworks, drivers, middlewares - everywhere.

Polymorphism Without Inheritance

You don't even need classes. JavaScript objects can be created on the fly - as long as the method name matches, dispatch works.

const logger = {
  log: (msg) => console.log(`${msg}`)
};
const alertSystem = {
  log: (msg) => console.log(`ALERT: ${msg}`)
};

function write(device, message) {
  device.log(message);   // polymorphic dispatch
}

write(logger, 'server started');      // server started
write(alertSystem, 'CPU overload');   // ALERT: CPU overload

That's it - you already understand polymorphic dispatch.

Your Polymorphic Toolkit

  • Define a contract (method name + signature) - informally or via interface/abstract class
  • Implement the contract in each concrete object/class
  • Call the method without checking type - let the language dispatch it
  • Extend forever: new implementations, zero changes to existing code