0
0
Typescriptprogramming~7 mins

Covariance and contravariance in Typescript

Choose your learning style9 modes available
Introduction

Covariance and contravariance help control how types relate when you use them in functions or collections. They make sure your program stays safe when you replace one type with another.

When you want to pass a more specific type where a general type is expected.
When you want to return a more specific type from a function that promises a general type.
When you want to accept function parameters that can handle more general types safely.
When working with arrays or collections that hold related types.
When designing APIs that accept or return functions with flexible type rules.
Syntax
Typescript
interface Producer<T> {
  produce: () => T;
}

interface Consumer<T> {
  consume: (item: T) => void;
}

TypeScript uses structural typing and does not have explicit in or out keywords like some languages, but it supports covariance and contravariance in function types.

Return types are usually covariant (you can return a subtype), and parameter types are contravariant (you can accept a supertype).

Examples
You can assign a function returning a Dog to a variable expecting a function returning an Animal because Dog has all Animal properties.
Typescript
type Animal = { name: string };
type Dog = { name: string; bark: () => void };

// Covariance example:
let getAnimal: () => Animal;
let getDog: () => Dog = () => ({ name: 'Rex', bark: () => console.log('Woof') });

getAnimal = getDog; // Allowed: Dog is subtype of Animal
You can assign a function that accepts an Animal to a variable expecting a function that accepts a Dog, because Dog is more specific.
Typescript
type Animal = { name: string };
type Dog = { name: string; bark: () => void };

// Contravariance example:
let feedDog: (dog: Dog) => void;
let feedAnimal: (animal: Animal) => void = (animal) => console.log(animal.name);

feedDog = feedAnimal; // Allowed: feedAnimal accepts any Animal, so it can feed a Dog
Arrays are covariant, so you can assign a Dog array to an Animal array variable, but this can cause runtime errors if you add incompatible types.
Typescript
let animals: Animal[] = [{ name: 'Cat' }];
let dogs: Dog[] = [{ name: 'Rex', bark: () => console.log('Woof') }];

// Arrays are covariant in TypeScript
animals = dogs; // Allowed but can be unsafe if you add a Cat to animals later
Sample Program

This program shows covariance by assigning a function returning Dog to a variable expecting a function returning Animal. It also shows contravariance by assigning a function accepting Animal to a variable expecting a function accepting Dog.

Typescript
type Animal = { name: string };
type Dog = { name: string; bark: () => void };

function getAnimal(): Animal {
  return { name: 'Generic Animal' };
}

function getDog(): Dog {
  return { name: 'Rex', bark: () => console.log('Woof') };
}

let animalProducer: () => Animal;
let dogProducer: () => Dog = getDog;

// Covariance: dogProducer can be assigned to animalProducer
animalProducer = dogProducer;

console.log(animalProducer().name); // Prints 'Rex'

function feedAnimal(animal: Animal) {
  console.log(`Feeding ${animal.name}`);
}

let dogFeeder: (dog: Dog) => void;

// Contravariance: feedAnimal can be assigned to dogFeeder
dogFeeder = feedAnimal;

dogFeeder({ name: 'Buddy', bark: () => console.log('Woof') });
OutputSuccess
Important Notes

Covariance means you can use a more specific type where a general type is expected, usually in return types.

Contravariance means you can use a more general type where a specific type is expected, usually in function parameters.

TypeScript checks these rules mostly in function types to keep your code safe.

Summary

Covariance lets you replace a type with a more specific one safely, mainly in outputs.

Contravariance lets you replace a type with a more general one safely, mainly in inputs.

Understanding these helps you write flexible and safe TypeScript code with functions and collections.