Skip to main content
The covariant modifier allows a subclass to override a mutable field (or a method parameter) with a type that is a subtype of the type defined in the superclass. This keyword explicitly disables static type safety checks regarding the input parameter of the overridden member, deferring type validation to runtime.

Syntax

The keyword is placed before the variable declaration in the subclass or before the parameter in a method signature.
class Container {
  num value;
  
  Container(this.value);
}

class IntContainer extends Container {
  // Overriding a mutable field with a narrower type (int is a subtype of num)
  @override
  covariant int value;

  IntContainer(this.value) : super(value);
}

Type System Mechanics

In Dart, return types (getters) are naturally covariant, meaning a subclass can return a subtype of the parent’s return type without special modifiers. However, method parameters and implicit field setters are contravariant or invariant to satisfy the Liskov Substitution Principle (LSP). Standard inheritance rules dictate that if a superclass accepts a parameter of type A, the subclass must also accept A. Narrowing the parameter to a subtype B (where B extends A) violates this contract because the subclass can no longer handle all inputs valid for the superclass. The covariant keyword instructs the analyzer to bypass this rule for mutable fields and parameters. It permits the subclass to tighten the input contract, accepting only a narrower type than the superclass.

Runtime Behavior and Safety

When a field is marked covariant, the compiler injects a check into the generated setter. If a value is assigned that matches the superclass’s signature but not the subclass’s narrowed signature, the runtime throws a TypeError. Consider the following class hierarchy:
class Animal {}
class Mouse extends Animal {}
class Cat extends Animal {}

class Shelter {
  Animal resident;
  Shelter(this.resident);
}

class MouseShelter extends Shelter {
  @override
  covariant Mouse resident; // Narrowing mutable field from Animal to Mouse
  
  MouseShelter(this.resident) : super(resident);
}

Valid Assignment

Direct assignment works when the assigned value matches the narrowed type.
void main() {
  MouseShelter shelter = MouseShelter(Mouse());
  shelter.resident = Mouse(); // Allowed statically and at runtime
}

Runtime Failure

If the subclass is upcast to the superclass, the static analyzer allows assignment of the wider type (Animal or Cat) because the variable is typed as Shelter. However, the runtime check enforces the covariant constraint of the underlying MouseShelter instance.
void main() {
  MouseShelter mouseShelter = MouseShelter(Mouse());
  
  // Upcast to superclass
  Shelter genericShelter = mouseShelter; 
  
  // Statically valid: Shelter expects an Animal. Cat is an Animal.
  // Runtime error: The underlying object is MouseShelter, which enforces 'covariant Mouse'.
  genericShelter.resident = Cat(); // Throws TypeError
}

Covariance in Methods

While frequently used with fields, the keyword technically applies to parameters. A mutable field declaration Type field; implicitly generates a setter set field(Type value). The covariant keyword applies to the value parameter of that implicit setter. It can also be applied explicitly to standard method parameters:
class Parent {
  void update(Parent p) {}
}

class Child extends Parent {
  @override
  void update(covariant Child c) {} // Narrowing parameter type
}
Master Dart with Deep Grasping Methodology!Learn More