Generic type variance helps control how types relate when used with generics. It makes your code safer and more flexible by defining if types can be replaced by their subtypes or supertypes.
Generic type variance in Typescript
// TypeScript does not support 'in' or 'out' keywords like some languages // Covariance is expressed with readonly properties // Contravariance is expressed with function parameter types
TypeScript does not use explicit 'in' or 'out' keywords like some languages.
Covariance often happens with readonly properties, contravariance with function parameters.
interface ReadonlyArray<T> {
readonly [index: number]: T;
}type Handler<T> = (arg: T) => void; // Function parameters are contravariant in TypeScript
ReadonlyArray<Dog> where ReadonlyArray<Animal> is expected.function processAnimals(animals: ReadonlyArray<Animal>) {} const dogs: ReadonlyArray<Dog> = [{ name: 'Buddy' }]; processAnimals(dogs);
This example shows covariance: a ReadonlyArray<Dog> can be passed to a function expecting ReadonlyArray<Animal> safely.
class Animal { name: string; constructor(name: string) { this.name = name; } } class Dog extends Animal { bark() { console.log(`${this.name} says Woof!`); } } function feedAnimals(animals: ReadonlyArray<Animal>) { animals.forEach(animal => console.log(`Feeding ${animal.name}`)); } const dogs: ReadonlyArray<Dog> = [new Dog('Buddy'), new Dog('Max')]; feedAnimals(dogs);
Covariance means you can use a more specific type where a more general type is expected.
Contravariance means you can use a more general type where a more specific type is expected.
TypeScript infers variance mostly from how types are used (e.g., readonly properties for covariance).
Generic type variance controls how generic types relate to each other.
Covariance allows substituting a subtype for a supertype safely.
Contravariance allows substituting a supertype for a subtype safely.