In object-oriented programming (OOP), polymorphism is key to creating flexible, scalable code by allowing us to work with objects through a common interface. While JavaScript lacks native support for interfaces and abstract classes, we can simulate these structures with patterns that enforce flexibility and structure in our code. In this post, we’ll explore how to simulate interfaces and abstract classes in JavaScript, enabling polymorphism to simplify complex applications.

What is Polymorphism?
Polymorphism allows us to treat different object types through a shared structure, or interface. For example, in a program with multiple animal types, each animal should have its own sound method. By treating them all as instances of a simulated Animal interface, we can easily handle various animal types without needing to rewrite code.
Simulating Interfaces and Abstract Classes in JavaScript
Since JavaScript doesn’t natively support interfaces or abstract classes, we can use patterns that enforce these behaviors.
1. Simulating Interfaces
To simulate an interface in JavaScript, we create a base class with unimplemented methods that throw errors. This ensures subclasses implement these methods, enforcing structure without providing functionality.
class Animal {
sound() {
throw new Error("Method 'sound()' must be implemented.");
}
}
class Dog extends Animal {
sound() { return "Woof!"; }
}
class Cat extends Animal {
sound() { return "Meow!"; }
}
function makeSound(animal) {
console.log(animal.sound());
}
makeSound(new Dog()); // Outputs: Woof!
makeSound(new Cat()); // Outputs: Meow!
Here, Animal simulates an interface by enforcing a sound method. Any subclass, such as Dog or Cat, must implement this method. The makeSound function works with any Animal, showcasing polymorphism by calling each animal’s unique sound.
2. Simulating Abstract Classes
To simulate an abstract class, we create a base class with implemented methods as well as methods that subclasses must define.
class Vehicle {
fuel() { return "Refueling..."; }
move() {
throw new Error("Method 'move()' must be implemented.");
}
}
class Car extends Vehicle {
move() { return "Driving on the road"; }
}
class Boat extends Vehicle {
move() { return "Sailing on the water"; }
}
function travel(vehicle) {
console.log(vehicle.fuel());
console.log(vehicle.move());
}
travel(new Car()); // Outputs: Refueling... Driving on the road
travel(new Boat()); // Outputs: Refueling... Sailing on the water
In this example, Vehicle simulates an abstract class by providing a fuel method with shared functionality while requiring subclasses to define move. This pattern allows polymorphism, letting travel work with any Vehicle type.
When to Use Each Simulation
- Interfaces are ideal for enforcing specific methods across unrelated classes, as we did with
Animal. - Abstract Classes are useful for sharing functionality across related classes while requiring specific methods, as we did with
Vehicle.
Conclusion
By simulating interfaces and abstract classes in JavaScript, we unlock the power of polymorphism and modularity even without native support. Try these patterns in your next project to experience firsthand how they bring flexibility and structure to your code.