flutter
/

Dart Polymorphism – A Complete Guide with Examples

Last Sync: Today

On this page

9
0%
5 min read
Remaining
5 minleft

Click any section to jump — progress syncs automatically

flutter

Dart Polymorphism – A Complete Guide with Examples

What is Polymorphism?

Polymorphism is a core concept in object‑oriented programming that allows objects of different types to respond to the same method call in their own way. The word comes from Greek meaning 'many forms'. In Dart, polymorphism enables you to write more flexible and reusable code by treating objects of different classes through a common interface.

Types of Polymorphism

Polymorphism is generally divided into two categories:

    • Compile‑time polymorphism (static binding) – achieved through method overloading or operator overloading. However, Dart does not support traditional method overloading based on different parameter lists. Instead, you can use optional parameters or named parameters to simulate similar behaviour.
    • Runtime polymorphism (dynamic binding) – achieved through method overriding, where a subclass provides its own implementation of a method defined in its superclass. This is the primary form of polymorphism in Dart.

Polymorphism through Inheritance (Method Overriding)

When a subclass overrides a method from its superclass, you can call that method on a variable of the superclass type that actually holds an instance of the subclass. At runtime, Dart determines which method to invoke based on the actual object type – this is called dynamic dispatch.

DARTRead-only
1
class Animal {
  void speak() {
    print('Animal makes a sound');
  }
}

class Dog extends Animal {
  @override
  void speak() {
    print('Dog barks');
  }
}

class Cat extends Animal {
  @override
  void speak() {
    print('Cat meows');
  }
}

void main() {
  Animal myPet = Dog();
  myPet.speak(); // Output: Dog barks

  myPet = Cat();
  myPet.speak(); // Output: Cat meows
}

In this example, myPet is declared as an Animal, but at runtime it refers to a Dog and then a Cat. The appropriate speak() method is called each time.

Polymorphism with Interfaces (Implicit Interfaces)

Every class in Dart implicitly defines an interface containing all its instance members. A class can implement one or more interfaces, which forces it to provide concrete implementations of those members. This allows different classes to be used interchangeably through the interface type.

DARTRead-only
1
class Flyable {
  void fly() {}
}

class Bird implements Flyable {
  @override
  void fly() {
    print('Bird flies');
  }
}

class Airplane implements Flyable {
  @override
  void fly() {
    print('Airplane flies');
  }
}

void makeItFly(Flyable thing) {
  thing.fly();
}

void main() {
  makeItFly(Bird());      // Bird flies
  makeItFly(Airplane());  // Airplane flies
}

The is and as Operators for Type Checking and Casting

Sometimes you need to check the actual type of an object at runtime. Use is (or is!) to test the type, and as to cast to a more specific type.

DARTRead-only
1
void describe(Animal animal) {
  if (animal is Dog) {
    print('It\'s a dog!');
  } else if (animal is Cat) {
    print('It\'s a cat!');
  }
}

void main() {
  Animal a = Dog();
  describe(a); // It's a dog!

  // Using as to cast
  Dog? dog = a as Dog?; // safe because we know a is a Dog
}

The covariant Keyword

When overriding a method, you can use the covariant keyword to narrow the type of a parameter. This tells Dart that the override accepts a more specific subtype of the original parameter type.

DARTRead-only
1
class Animal {}
class Dog extends Animal {}

class AnimalHandler {
  void handle(Animal animal) {
    print('Handling animal');
  }
}

class DogHandler extends AnimalHandler {
  @override
  void handle(covariant Dog animal) {
    print('Handling dog');
  }
}

void main() {
  DogHandler handler = DogHandler();
  handler.handle(Dog()); // OK
}

Polymorphism with dynamic (Use with Caution)

Dart's dynamic type disables static type checking, allowing any method call. This is a form of polymorphism, but it sacrifices type safety. Prefer using interfaces or inheritance instead.

DARTRead-only
1
void main() {
  dynamic thing = Dog();
  thing.speak(); // Works, but no type safety
  thing.unknownMethod(); // Runtime error
}

Polymorphism with Generics

Generics allow you to write code that works with a variety of types while preserving type safety. For example, a List<T> can hold any type, but you still get static checking when you use it.

DARTRead-only
1
T identity<T>(T value) {
  return value;
}

void main() {
  print(identity<int>(42));
  print(identity<String>('Hello'));
}

Key Takeaways

    • Polymorphism allows objects of different types to be treated uniformly through a common interface.
    • In Dart, the main form of polymorphism is runtime polymorphism achieved by overriding methods.
    • Dart does not support traditional method overloading; use optional/named parameters instead.
    • Use implements to enforce interface contracts and achieve polymorphism across unrelated classes.
    • The is and as operators help with type checking and casting.
    • covariant lets you narrow parameter types in overrides.
    • Prefer using explicit interfaces over dynamic for type safety.

Try it yourself

class Shape {
  void draw() {
    print('Drawing a shape');
  }
}

class Circle extends Shape {
  @override
  void draw() {
    print('Drawing a circle');
  }
}

class Square extends Shape {
  @override
  void draw() {
    print('Drawing a square');
  }
}

void main() {
  List<Shape> shapes = [Circle(), Square()];
  for (var shape in shapes) {
    shape.draw();
  }
}

Test Your Knowledge

Q1
of 4

What is the output of this code? class A { void foo() { print('A'); } } class B extends A { @override void foo() { print('B'); } } void main() { A obj = B(); obj.foo(); }

A
A
B
B
C
Error
D
Nothing
Q2
of 4

Which keyword is used to narrow a parameter type in an overridden method?

A
narrow
B
covariant
C
specific
D
specialize
Q3
of 4

Does Dart support compile‑time polymorphism through method overloading?

A
Yes, fully
B
Only with dynamic
C
No, but you can use optional parameters
D
Yes, using the `overload` keyword
Q4
of 4

What does the `as` operator do?

A
Checks the type of an object
B
Casts an object to a type
C
Creates a new object
D
Assigns a value

Frequently Asked Questions

Does Dart support method overloading?

No, Dart does not support traditional method overloading where multiple methods share the same name but have different parameter signatures. You can simulate similar behaviour using optional parameters, named parameters, or by using dynamic (though not recommended).

What is the difference between `extends` and `implements` in terms of polymorphism?

extends creates a subclass that inherits the superclass's implementation; you can override methods to provide polymorphic behaviour. implements forces a class to provide its own implementation of all members of the interface; it also enables polymorphism through that interface type, but without code reuse.

Can I achieve polymorphism without inheritance?

Yes, by using interfaces (implicitly via implements) or by using mixins. Any class that implements the same interface can be used polymorphically through that interface type.

What is the `covariant` keyword and when should I use it?

covariant allows you to narrow the type of a parameter when overriding a method. Use it when you want an override to accept a more specific subtype than the original method. It tells the analyzer that the override is intentionally more restrictive.

How does polymorphism work with collections?

You can have a collection of a supertype (e.g., List<Animal>) that holds instances of various subtypes (e.g., Dog, Cat). When you iterate and call a method, the appropriate overridden version is called.

What is dynamic dispatch?

Dynamic dispatch is the mechanism by which a call to an overridden method is resolved at runtime based on the actual type of the object, not the reference type. This is what enables runtime polymorphism.

Is it better to use `dynamic` or interfaces for polymorphism?

Always prefer interfaces or inheritance over dynamic. dynamic bypasses static type checking and can lead to runtime errors. Interfaces provide a contract and maintain type safety.

Previous

dart inheritance

Next

dart abstraction

Related Content

Need help?

Explore our comprehensive docs or start a chat with our tech experts.