What is Polymorphism?
Polymorphism (from Greek: "many forms") means that a single interface can represent objects of different types. In Dart, polymorphism lets you write code that works with a parent type, while at runtime different subclass behaviors execute. This is the foundation of flexible, extensible object-oriented design.
Subtype Polymorphism
A subclass object can be used anywhere the parent class type is expected:
class Animal {
String name;
Animal(this.name);
// This method will be overridden by subclasses
String speak() => ''...'';
void describe() => print(''$name says: \$${speak()}'');
}
class Dog extends Animal {
Dog(String name) : super(name);
@override String speak() => ''Woof!'';
}
class Cat extends Animal {
Cat(String name) : super(name);
@override String speak() => ''Meow!'';
}
class Parrot extends Animal {
final String phrase;
Parrot(String name, this.phrase) : super(name);
@override String speak() => phrase;
}
void main() {
// All stored as Animal type — polymorphic
List<Animal> animals = [
Dog(''Rex''),
Cat(''Whiskers''),
Parrot(''Polly'', ''Polly wants a cracker!''),
Dog(''Buddy''),
];
for (var animal in animals) {
animal.describe(); // correct speak() called at runtime
}
}
Output:Rex says: Woof!
Whiskers says: Meow!
Polly says: Polly wants a cracker!
Buddy says: Woof!
Runtime Type Dispatch
Dart dispatches method calls based on the actual runtime type of the object, not the declared type. Use is to check types and as to cast:
void processAnimal(Animal animal) {
animal.describe(); // polymorphic
// Type-specific logic with is
if (animal is Dog) {
print('' $${animal.name} can fetch!'');
} else if (animal is Parrot) {
print('' $${animal.name} can mimic: "$${animal.phrase}"'');
}
}
void main() {
processAnimal(Dog(''Max''));
processAnimal(Cat(''Luna''));
processAnimal(Parrot(''Rio'', ''Pretty bird!''));
}
Interface-Based Polymorphism
abstract class Drawable {
void draw();
String get description;
}
class Circle extends Drawable {
final double radius;
Circle(this.radius);
@override void draw() => print(''Drawing circle r=$radius'');
@override String get description => ''Circle(r=$radius)'';
}
class Square extends Drawable {
final double side;
Square(this.side);
@override void draw() => print(''Drawing square s=$side'');
@override String get description => ''Square(s=$side)'';
}
class Triangle extends Drawable {
final double base, height;
Triangle(this.base, this.height);
@override void draw() => print(''Drawing triangle b=$base, h=$height'');
@override String get description => ''Triangle(b=$base, h=$height)'';
}
// Works with ANY Drawable — polymorphic
void drawAll(List<Drawable> shapes) {
for (var shape in shapes) {
shape.draw();
print('' -> \$${shape.description}'');
}
}
void main() {
drawAll([Circle(5), Square(4), Triangle(3, 6), Circle(2)]);
}
Full Example: Payment Processing
abstract class PaymentMethod {
String get name;
bool validate();
Future<bool> processPayment(double amount);
}
class CreditCard extends PaymentMethod {
final String cardNumber;
final String cvv;
CreditCard(this.cardNumber, this.cvv);
@override String get name => ''Credit Card'';
@override bool validate() => cardNumber.length == 16 && cvv.length == 3;
@override
Future<bool> processPayment(double amount) async {
await Future.delayed(Duration(milliseconds: 200));
print(''Credit card charged: \$\$${amount.toStringAsFixed(2)}'');
return true;
}
}
class PayPal extends PaymentMethod {
final String email;
PayPal(this.email);
@override String get name => ''PayPal'';
@override bool validate() => email.contains(''@'');
@override
Future<bool> processPayment(double amount) async {
await Future.delayed(Duration(milliseconds: 150));
print(''PayPal transfer: \$\$${amount.toStringAsFixed(2)} from $email'');
return true;
}
}
class PaymentProcessor {
// Works with any PaymentMethod — polymorphic
Future<void> charge(PaymentMethod method, double amount) async {
print(''Processing via \$${method.name}...'');
if (!method.validate()) {
print(''Validation failed for \$${method.name}'');
return;
}
final success = await method.processPayment(amount);
print(success ? ''Payment successful!'' : ''Payment failed.'');
}
}
void main() async {
var processor = PaymentProcessor();
await processor.charge(CreditCard(''1234567812345678'', ''123''), 49.99);
await processor.charge(PayPal(''user@example.com''), 29.99);
}
Output:Processing via Credit Card...
Credit card charged: $49.99
Payment successful!
Processing via PayPal...
PayPal transfer: $29.99 from user@example.com
Payment successful!
Exercise
- Create an abstract
Notification class with send(String message). Implement EmailNotification, SMSNotification, and PushNotification. Write a NotificationService that sends to a list of Notification objects.
- Create a
Serializer abstract class with serialize(Map data) and deserialize(String input). Implement JsonSerializer and CsvSerializer.
Quiz
1. What does polymorphism mean in OOP?
- A) A class with many fields
- B) An object that can take many forms — one interface, multiple implementations ✅
- C) Multiple inheritance
- D) A method with many parameters
2. What determines which overridden method is called at runtime?
- A) The declared (compile-time) type
- B) The actual runtime type of the object ✅
- C) The order of class declaration
- D) The variable name
3. What Dart keyword checks whether an object is a certain type?
- A) typeof
- B) as
- C) is ✅
- D) instanceof
Summary
Polymorphism allows one interface to represent many types. Store different subclass objects in a parent-type list and call methods — each object responds with its own overridden behavior. Use is for runtime type checks (with automatic smart cast) and as for explicit downcasting. Interface-based polymorphism (via abstract class + implements) keeps code flexible and testable — swap implementations without changing calling code.