TypeScript
Beginner
1 min read
Variance, Covariance, and Contravariance
Example
// Covariance — return types
type Producer<T> = () => T;
// Dog is a subtype of Animal
class Animal { name: string = "animal" }
class Dog extends Animal { breed: string = "lab" }
// Producer<Dog> is assignable to Producer<Animal> (covariant)
const dogProducer: Producer<Dog> = () => new Dog();
const animalProducer: Producer<Animal> = dogProducer; // OK
// Contravariance — parameter types
type Consumer<T> = (value: T) => void;
// Consumer<Animal> is assignable to Consumer<Dog> (contravariant)
const animalConsumer: Consumer<Animal> = (a) => console.log(a.name);
const dogConsumer: Consumer<Dog> = animalConsumer; // OK — handles more than needed
// Explicit variance annotations (TypeScript 4.7+)
type CovariantBox<out T> = { get(): T };
type ContravariantBox<in T> = { set(value: T): void };
type InvariantBox<T> = { get(): T; set(value: T): void };
// CovariantBox<Dog> extends CovariantBox<Animal>
const dogBox: CovariantBox<Dog> = { get: () => new Dog() };
const animalBox: CovariantBox<Animal> = dogBox; // OK
// Method vs function property — different variance
interface WithMethod { method(s: string): void } // bivariant (unsound)
interface WithProperty { prop: (s: string) => void } // contravariant (sound)
// Readonly arrays are covariant, mutable arrays are not
const dogs: Dog[] = [new Dog()];
const animals: readonly Animal[] = dogs; // OK (readonly covariance)
// const mutableAnimals: Animal[] = dogs; // Error in strict mode
Related Resources
TypeScript Reference
Complete tag & property list
TypeScript How-To Guides
Step-by-step practical guides
TypeScript Exercises
Practice what you've learned
More in TypeScript