# Type Variance and TypeScript

Coming from a dynamic language (JavaScript, for example) into a type-safe language can be a frustrating experience. Your shoot-from-the-hip programming is replaced with carefully thought-out type definitions and--sometimes--painfully slow logic creation. But, in the end, you can end up with a fewer percentage of bugs (~15%) shipped to production.

I would frequently read the terms 'contravariance', 'covariance', and 'invariance' in nitty-gritty details of type-safe language implementation (TypeScript being one of them), but I failed to wrap my head around some of their more formal definitions. Take this example from Wikipedia:

``````Within the type system of a programming language, a typing rule or a type constructor is:

- covariant if it preserves the ordering of types (≤), which orders types from more specific to more generic;

- contravariant if it reverses this ordering;

- bivariant if both of these apply (i.e., both I<A> ≤ I<B> and I<B> ≤ I<A> at the same time);

- invariant or nonvariant if neither of these applies.``````

* furrows brow in a vain attempt to understand the situation *

Now that I believe I understand these concepts to a small degree, I'm going to try to explain it in different language. Hopefully someone who was/is having conceptual difficulties with these terms will find it useful.

First, it's helpful that a newcomer to these concepts understands some basic Set theory. But really, it's easy. We're talking very basic Set theory. We need in our vocabulary the terminology: superset, subset, and of course set.

#### Set

A group of "things" classified in any way we want. We could have a set of Mammals or a set of blonde-haired actors. It's arbitrarily defined. (Of course, there are also formal definitions, but that's not important for the time being)

#### Superset

A larger group of "things" which encompasses (has as a part of itself) another set. We could have a set of cats and a superset of Mammals. All cats are Mammals, thus Mammals would be the superset of cats.

#### Subset

The inverse of superset, the set within another set. Using our example above: cats would be a subset of Mammals.

Okay, but how does this relate to programming languages?

#### Types

Hopefully, we--as programmers--intuitively understand what a "type" is in the context of a programming language. Specifically, TypeScript's (TypeScript, by the way, is a superset of JavaScript) types are:

• `unknown`
• `undefined`
• `void`
• `any`

• `null`
• `number`
• `enums`

• `bigint`
• `boolean`
• `string`
• `symbol`
• `object`
• `array`

• `tuple`

• `function`
• `constructor`

Here, nesting signifies a super/subset relationship

So we see that almost everything is a subset of `any`, `tuple` is a subset of `array`, which is a subset of `object`, and so on. We also have `never` (not listed above) which is a subtype of everything and a superset of nothing, i.e. it is the innermost set.

This relationship amongst the primitives is easily grasped, I think. What's more interesting is when we start to construct our own types. Let's see an example:

``type OurNewType = number | object``

It's easy to figure out what the subset (i.e. subtype) of `tuple` is (answer: `never`), but what about `OurNewType`? This isn't nearly as complicated as it can get, but it's still worth the exercise. We can figure the subtypes of `OurNewType` by first looking at its constituents: `number` and `object`. What are their subtypes? Looking at the relationships noted above we see we have `array` and `tuple` as a subtype of `object`, and we also have `enums` as a subtype of `number`. And, again, `never`, which always tags along for the ride as being in the list of subtypes. So we definitely know that we have four subtypes. But further, we also have the top-level constituents of `OurNewType` as well, i.e. `object` and `number`. So `number` is a subtype of `OurNewType` as is `object`. Together these two form a set so, naturally, they're a subset of the set they help compose.

To complicate matters more (maybe?) we also have the notion of supertypes and subtypes (supersets and subsets) of objects we define. In other words, types we create that aren't explicitly composed of primitives (like `OurNewType` is). Particularly, I'm referring to classes (in the Object Oriented sense).

Let's go back to the beginning of this article when I mentioned that Cats was a subset of Mammals. For illustration let's create another set, which will be a superset of Mammal and we'll call it...oh, I dunno...Chordata. Let's now create these classes, in TypeScript:

``````class Chordate {
hasSpine() {
return true
}
}

class Mammal extends Chordate {
hasLiveYoung() {
return true
}
}

class Cat extends Mammal {
meows() {
return true
}
}

const kitty: Cat = new Cat()

kitty.meows()        // true
kitty.hasLiveYoung() // true
kitty.hasSpine()     // true``````

The supertype/subtype relationship is easy to see here. Since `Cat` extends `Mammal`, which extends `Chordate`, `Cat` "has access" to the attributes of those which it extends from. This does not work bi-directionally, as also seems obvious:

``````const someChordate: Chordate = new Chordate()

someChordate.meows() // true? false? Guess it depends...``````

The point is, since `Chordate` is a supertype of `Cat` it (`someChordate`) could be a `Cat`, but it isn't "for sure". It could be a fish, for example...which, cat fish puns aside, don't meow. In mathematical notation (Sorry, this makes it less verbose. Plus, it looks cool.) we describe this relationship as:

\$\$ Cat \subset Chordate \$\$

Which says that `Cat` is a subset (subtype) of `Chordate`. We can also symbolize "subset or equal set" like so:

\$\$ A \subseteq B \$\$

Which says `A` is a subset of (or the same set as) `B`. With these ideas and ways of expressing them in hand we can move forward with the meat of this post.

So, what is...

#### Covariance

The ability of an Object or Class to accept types which are subtypes (or equivalent types) to a given set of parameters. For instance, if a class constructor accepts a parameter of type `P` and we supply an argument of type `A` TypeScript (as an example) enforces that

\$\$ A \subseteq P \$\$

otherwise it won't transpile our code.

#### Contravariance

This is the opposite of Covariance. Instead of subtype acceptance we're looking at supertype acceptance. It's important to note that in TypeScript only functions are contravariant. That is, functions only accept arguments of type `A` if their relationship to the parameter type `P` is

\$\$ A \supseteq P \$\$

Weird, right? That seems counter-intuitive. It isn't. But it certainly feels that it is.

These last two are easy...

#### Bivariance

It doesn't matter if it's a supertype or subtype, either will work. A silly example of a bivariant function would be

``function justReturnIt(thing: any): any { return thing }``

#### Invariance

This is the need for exactly a specific type. Say you have a function that concatenates two strings

``function concat(s1: string, s1: string): string { /* blah blah */ }``

We would say this function is invariant with respect to its types. In other words, give it exactly what it wants.