# Covariant, Contravariant, and Invariant in Typescript

Sandro Maglione

Get in touch with meWeb development

27 October 2023

β’11 min read

Sandro Maglione

Web development

**Variance** in Typescript specifies how a generic type `F<T>`

varies with respect to its type parameter `T`

:

If `T extends U`

, variance allows to know how `F<T>`

and `F<U>`

are related:

**Covariant**:`F<T> extends F<U>`

**Contravariant**:`F<U> extends F<T>`

**Invariant**: Neither covariant nor contravariant**Bivariant**:`F<T> extends F<U>`

and`F<U> extends F<T>`

Enough with abstract definitions. Let's see how this works in practice π

## How variance works in Typescript

We are going to understand variance in practice using the following types:

```
interface Animal {
animalStuff: any;
}
interface Dog extends Animal {
dogStuff: any;
}
type Getter<T> = () => T; // Covariant
type Setter<T> = (value: T) => void; // Contravariant
type Inv<T> = (value: T) => T; // Invariant
```

We use a function type with a generic parameter `T`

.

In the example we have `Animal`

and `Dog`

, where `Dog extends Animal`

. Variance allows to define the relation between `Getter<Animal>`

and `Getter<Dog>`

(same for `Setter`

and `Inv`

).

If `Dog extends Animal`

, is it still true that `Getter<Dog> extends Getter<Animal>`

?

Let's see. We are going to show how variance works with a practical example π

## Getter function

An example of Covariant is the getter function:

`type Getter<T> = () => T; // Covariant`

This function takes no parameters and returns a value of a generic type `T`

.

### Covariant

Covariant means that when `Dog extends Animal`

then also `Getter<Dog> extends Getter<Animal>`

applies.

We can prove this by implementing a `Getter<Dog>`

:

`const getDog: Getter<Dog> = () => ({ animalStuff: "", dogStuff: "" });`

We then define a function that takes a `Getter<Animal>`

as parameter:

`const withGetAnimal = (get: Getter<Animal>) => get().animalStuff;`

Can we call `withGetAnimal`

with `getDog`

as parameter? Since `Getter`

is covariant, the answer is yes:

```
withGetAnimal((): Animal => {
return getDog(); // () => Dog
}); // Covariant
```

`withGetAnimal`

requires a function that returns an `Animal`

(`Getter<Animal>`

). Since `getDog`

returns a `Dog`

, which is an `Animal`

since `Dog extends Animal`

, then this works correctly.

Therefore since `Dog extends Animal`

then also `Getter<Dog> extends Getter<Animal>`

applies.

### Contravariant

Does the opposite also apply?

If `Dog extends Animal`

can we conclude that `Getter<Animal> extends Getter<Dog>`

?

Notice how

`Animal`

and`Dog`

are inverted compared to before.

Let's do the same as we did in the example above, with `getAnimal`

instead of `getDog`

:

```
const getAnimal: Getter<Animal> = () => ({ animalStuff: "" });
const withGetDog = (get: Getter<Dog>) => get().dogStuff;
```

Can we pass `getAnimal`

to `withGetDog`

? The answer is no:

```
withGetDog((): Dog => {
// βοΈ Property 'dogStuff' is missing in type 'Animal' but required in type 'Dog'
return getAnimal(); // () => Animal
}); // No Contravariant
```

`withGetDog`

requires a function that returns a `Dog`

(`Getter<Dog>`

). `getAnimal`

returns an `Animal`

, but `Animal`

is not a `Dog`

, since it is missing the `dogStuff`

property:

```
interface Animal {
animalStuff: any;
}
interface Dog extends Animal {
dogStuff: any; // π `Animal` is not `Dog`
}
```

Therefore `Dog extends Animal`

does not imply `Getter<Animal> extends Getter<Dog>`

.

### There is more.

Every week I build a new open source project, with a new language or library, and teach you how I did it, what I learned, and **how you can do the same**. Join me and other 400+ readers.

## Setter function

An example of Contravariant instead is the setter function:

`type Setter<T> = (value: T) => void; // Contravariant`

This function takes a single parameters of type `T`

and returns `void`

.

### Covariant

Just as before, we define a `Setter<Dog>`

function:

```
const setDog: Setter<Dog> = (dog) => {
dog.dogStuff = 1;
};
```

And also a function that takes a `Setter<Animal>`

as parameter:

`const withSetAnimal = (set: Setter<Animal>) => set({ animalStuff: "" });`

Can we pass `setDog`

to `withSetAnimal`

? The answer is no:

```
withSetAnimal((animal /** Animal */) => {
// βοΈ Argument of type 'Animal' is not assignable to parameter of type 'Dog'
setDog(animal); // (value: Dog) => void
}); // Contravariant
```

`withSetAnimal`

gives us an `Animal`

as parameter. However, `getDog`

requires a `Dog`

. As we said previously, `Animal`

is not a `Dog`

, since it is missing the `dogStuff`

property:

```
interface Animal {
animalStuff: any;
}
interface Dog extends Animal {
dogStuff: any; // π `Animal` is not `Dog`
}
```

We cannot pass `animal`

to `setDog`

. Therefore `Dog extends Animal`

does not imply `Setter<Dog> extends Setter<Animal>`

, `Setter`

is **not** covariant.

### Contravariant

We do the same inverting `Dog`

and `Animal`

, defining a `setAnimal`

function this time:

```
const withSetDog = (set: Setter<Dog>) => set({ animalStuff: "", dogStuff: "" });
const setAnimal: Setter<Animal> = (animal) => {
animal.animalStuff = 1;
};
```

`withSetDog`

accepts `setAnimal`

? The answer is yes:

```
withSetDog((dog /** Dog */) => {
setAnimal(dog); // (value: Animal) => void
}); // Contravariant
```

`withSetDog`

gives us a `Dog`

. Since `setAnimal`

accepts an `Animal`

, and since `Dog`

is an `Animal`

(`Dog extends Animal`

), we can pass `dog`

to `setAnimal`

.

`Setter`

is contravariant: `Dog extends Animal`

implies that `Setter<Animal> extends Setter<Dog>`

.

## Invariance

An example of Invariance is the following:

`type Inv<T> = (value: T) => T; // Invariant`

A function that takes a parameter of type `T`

and returns `T`

.

Same as before we define a `Inv<Dog>`

, `withAnimalInv`

.

We can see that we cannot apply `invDog`

to `withAnimalInv`

:

```
const withAnimalInv = (inv: Inv<Animal>) => inv({ animalStuff: "" });
const invDog: Inv<Dog> = (dog) => ({ animalStuff: "", dogStuff: "" });
withAnimalInv((animal): Animal => {
// βοΈ Argument of type 'Animal' is not assignable to parameter of type 'Dog' (Input type!)
return invDog(animal); // (value: Dog) => Dog
});
```

`withAnimalInv`

provides an `Animal`

and expects a return type of `Animal`

. `invDog`

indeed returns a `Dog`

, which is a valid `Animal`

. However, `invDog`

also requires a `Dog`

as parameter, so `animal`

is not valid.

Therefore `Inv<T>`

is **not** covariant.

We do the same for contravariance by defining `invAnimal`

:

```
const withDogInv = (inv: Inv<Dog>) => inv({ animalStuff: "", dogStuff: "" });
const invAnimal: Inv<Animal> = (animal) => ({ animalStuff: "" });
withDogInv((dog): Dog => {
// βοΈ Type 'Animal' is not assignable to type 'Dog' (Return type!)
return invAnimal(dog); // (value: Animal) => Animal
});
```

`withDogInv`

provides a `Dog`

. In this case we can pass `dog`

to `invAnimal`

since it requires an `Animal`

. This time instead the error comes from the return type: `withDogInv`

requires a `Dog`

as return type, but `invAnimal`

returns an `Animal`

.

Therefore `Inv<T>`

is **not** contravariant.

Since `Inv<T>`

is **not** covariant and **not** contravariant, we say that `Int<T>`

is invariant:

`Dog extends Animal`

does**not**imply that`Inv<Dog> extends Inv<Animal>`

`Dog extends Animal`

does**not**imply that`Inv<Animal> extends Inv<Dog>`

### There is more.

Every week I build a new open source project, with a new language or library, and teach you how I did it, what I learned, and **how you can do the same**. Join me and other 400+ readers.

## Variance and composition: Union and Intersection types

Variance is relevant also to understand composition in typescript:

Contravariant parameters composed as an intersection (

`&`

) are equivalent to covariant parameters composed together as a union (`|`

)for purposes of assignability

Simply put, this principle relates `<A>`

, `<A | B>`

, and `<A & B>`

when assigning variables:

- If I have
`a = Type<A>`

, can I assign`a`

to`b: Type<A & B>`

? - If I have
`a = Type<A>`

, can I assign`a`

to`b: Type<A | B>`

?

This assignability depends on variance.

Enough theory, let's see a concrete example to understand why this matter π

### Union types

We are going to use `Getter`

, `Setter`

, and `Inv`

again:

```
type Getter<T> = () => T; // Covariant
type Setter<T> = (value: T) => void; // Contravariant
type Inv<T> = (value: T) => T; // Invariant
```

Let's see an example for all of them.

```
const getterUnion = <A, B>(a: Getter<A>): Getter<A | B> => {
/**
* `a`: function that returns `A`
* `b`: function that returns `A | B`
*
* It works since `A` is "included" in `A | B`
*/
const b: Getter<A | B> = a;
return b;
};
```

It is possible to assign a variable of type `Getter<A>`

to a variable of type `Getter<A | B>`

.

`Getter`

is a function that returns the given type. `A | B`

means "A or B". Since `Getter<A>`

returns `A`

, its value can be assigned to `A | B`

.

```
const setterUnion = <A, B>(a: Setter<A>): Setter<A | B> => {
/**
* βοΈ 'B' could be instantiated with an arbitrary type which could be unrelated to 'A'
*
* `b`: (_: A | B) => void
*/
const b: Setter<A | B> = a;
return b;
};
```

With `Setter`

this assignability does not work. `Setter`

requires a parameter of type `A | B`

, but `Setter<A>`

provides only `A`

:

```
/**
* `a` is of type `{ a: number }`, but we provided `{ b: string }`
* so there is no way to actually have a valid `{ a: number }` for the setter π€·πΌββοΈ
*/
setterUnion<{ a: number }, { b: string }>((a) => {})({ b: "" });
```

In the example above we provide a type of `{ b: string }`

, but `Setter<A>`

expects a type of `{ a: number }`

, which is not present. Therefore assignability does not work.

```
const invUnion = <A, B>(a: Inv<A>): Inv<A | B> => {
/** βοΈ 'A' could be instantiated with an arbitrary type which could be unrelated to 'A | B' */
const b: Inv<A | B> = a;
return b;
};
```

`Inv`

requires both input and output to be of type `A | B`

, so it is definitely not possible to assign `A`

.

### Intersection types

We do the same analysis for intersection types (`&`

):

```
const getterIntersection = <A, B>(a: Getter<A>): Getter<A & B> => {
/**
* βοΈ 'B' could be instantiated with an arbitrary type which could be unrelated to 'A'
*
* `b` returns something that requires both `A` and `B`, but `a` provides only `A`
*/
const b: Getter<A & B> = a;
return b;
};
```

This time `Getter<A>`

cannot be assigned to `Getter<A & B>`

.

`Getter<A & B>`

returns `A & B`

, "A and B", but `Getter<A>`

provides only `A`

. No assignability then.

```
const setterIntersection = <A, B>(a: Setter<A>): Setter<A & B> => {
/**
* `a`: (_: A) => void
* `b`: (_: A & B) => void
*
* Input parameters will be provided later
* For now `A` is included in `A & B`, therefore this works
*/
const b: Setter<A & B> = a;
return b;
};
```

The inverse is valid also for `Setter`

.

`Setter`

requires a parameter of type `A & B`

, "A and B". For `Setter<A>`

instead a parameter of type `A`

is enough. Since `Setter<A & B>`

gets access to both `A`

and `B`

, `A`

is available for `Setter<A>`

, so this works:

```
/**
* Calling the function requires both `{ a: number }` and `{ b: string }`.
*
* We then pass `{ a: number }` to the first setter, this works β
*/
setterIntersection<{ a: number }, { b: string }>((a) => {})({ b: "", a: 0 });
```

Finally `Inv`

:

```
const invIntersection = <A, B>(a: Inv<A>): Inv<A & B> => {
/** βοΈ Type 'A' is not assignable to type 'A & B' */
const b: Inv<A & B> = a;
return b;
};
```

Same as before in this case, `Inv<A>`

cannot be assigned to `Inv<A & B>`

.

### Assignability and Higher-Kinded Types

This principle is relevant to understand the encoding of Higher-Kinded Types in Typescript (from Effect):

```
export type Kind<F extends TypeLambda, In, Out2, Out1, Target> = F extends {
readonly type: unknown
}
? (F & {
readonly In: In
readonly Out2: Out2
readonly Out1: Out1
readonly Target: Target
})["type"]
: {
readonly F: F
readonly In: (_: In) => void // Contravariant
readonly Out2: () => Out2 // Covariant
readonly Out1: () => Out1 // Covariant
readonly Target: (_: Target) => Target // Invariant
}
```

In the `Effect<R, E, A>`

the above encoding defines how union (`|`

) and intersection (`&`

) are assignable to each generic parameter.

We are going to learn the details of Higher-Kinded Types and their encoding in Typescript in a follow up article π

That's it!

You can find the full code at this Playground Link.

Now you have a good grasp of **variance in Typescript and how it relates to assignability**.

These are definitely more **advanced concepts**, not strictly necessary in your day to day work, but important to understand how Typescript works in all its aspects.

If you are interested in more advanced and beginner content of Typescript you can subscribe to the newsletter below π

Thanks for reading.