Understand Type Variances in 30 Seconds
Tags: computerscience, inthirtyseconds, scala, java
Published: 2020-08-09 21:33:20

__Type variances__ are a topic which most Java developers don't usually think about, but which can take some getting used to for those transitioning into languages which do make heavy use of them, like Scala.

There are three possible kinds of type variance

1. invariance
2. covariance
3. contravariance

Recall that, in Scala, the notation `T <: S` [means that `T` `extends` or "subclasses" `S`](https://stackoverflow.com/a/6829010/2925434), while `T >: S` means the converse -- [that `S` extends or subclasses `T`](https://www.bradcypert.com/upper-and-lower-bounds-in-scala/).

You may notice that `T >: S` means the same as `S <: T`, so why are there two different ways to say this? Well, you might want to define both an upper (`<:`) _and_ a lower (`>:`) bound for your type. For example, if you wrote a class hierarchy that mirrored [taxonomic ranks](https://en.wikipedia.org/wiki/Taxonomic_rank), you might want a type that is larger than `Species`, but less than `Order`, in which case you would write `Genus <: T <: Family`.

You can think of `T <: S` as meaning "`T` is a type which is _less than or equal to_ type `S`" (in a subclassing / extending sense), while `T >: S` means that "`T` is _greater than or equal to_ type `S`". Since there are no taxonomic ranks _between_ `Genus` and `Family`, `Genus <: T <: Family` means that type `T` can be _either_ `Genus` or `Family`.

### Variance

It's important to remember that when we talk about _variance_, we're talking about the types _themselves_, but also the "container types" which are parameterized by them.

What this means is that you can't talk about type variance without some parameterized type like [`Set[A]`](https://www.scala-lang.org/api/current/scala/collection/immutable/Set.html) or [`List[+A]`](https://www.scala-lang.org/api/current/scala/collection/immutable/List.html) or [`Map[K, +V]`](https://www.scala-lang.org/api/current/scala/collection/immutable/Map.html). We're interested in the parameterized types `Set`, `List`, and `Map`, as they relate to their type parameters.

### Invariance

A parameterized type is _invariant_ in its type parameter if there is __no subclassing relationship__ when using different type parameters. For example, in Scala, `Set[A]` is invariant in its type parameter `A`. This means that

```
    A <: B  =>  Set[A] <: Set[B] is false
    A >: B  =>  Set[A] >: Set[B] is false
    A <: B  =>  Set[A] >: Set[B] is false
    A >: B  =>  Set[A] <: Set[B] is false
```

In other words, no matter the relationship between `A` and `B`, you cannot substitute a `Set[A]` for a `Set[B]` and vice versa. A `Set[String]` _is not_ a `Set[Any]`, for example, and a `Set[Numeric]` _is not_ a `Set[Int]`.

If you're coming from a Java background, this is the variance you're probably most familiar with. [Java was initially developed without generics (no type parameters!)](https://dev.to/awwsmm/types-vs-classes-39an), so invariance was really the only solution.

### Covariance

A parameterized type is _covariant_ in its type parameter if there is __a parallel subclassing relationship__ when using different type parameters. For example, in Scala, `List[+A]` is covariant in its type parameter `+A`. (That's what the `+` means before the generic type `A`.) This means that

```
    A <: B  =>  List[A] <: List[B] is true
    A >: B  =>  List[A] >: List[B] is true
    A <: B  =>  List[A] >: List[B] is false
    A >: B  =>  List[A] <: List[B] is false
```

This is the more "intuitive" type variance, in my opinion. In this case, we might have a subclassing relationship like `Dog <: Animal`, because a `Dog` _is a kind of_ `Animal`. In that case, a `List[Dog] <: List[Animal]` because a list of dogs is a kind of list of animals.

Wherever we require a `List[Animal]`, we can provide a `List[Dog]`, because `List[Dog] <: List[Animal]` (read as "`List` of `Dog` is _a kind of_ `List` of `Animal`").

### Contravariance

Contravariance is the "opposite" of covariance. A parameterized type is _contravariant_ in its type parameter if there is __an anti-parallel subclassing relationship__ when using different type parameters. For a parameterized type which is contravariant in its type parameter, the following relationships hold

```
    A <: B  =>  List[A] <: List[B] is false
    A >: B  =>  List[A] >: List[B] is false
    A <: B  =>  List[A] >: List[B] is true
    A >: B  =>  List[A] <: List[B] is true
```

This one is a bit more difficult to explain.

Imagine we have a parameterized `Teacher[-S]` class, where `S` gives the `Subject` the `Teacher` is qualified to teach. The `-` indicates that `Teacher` is _contravariant_ in its type parameter `S`.

As for the `Subject`, `S`, we might say that `TypeVariance` is _a kind of_ programming concept

```
TypeVariance <: ProgrammingConcept
```

...both of which are `Subject`s which our `Teacher` might teach.

If our `Teacher` is qualified to teach `ProgrammingConcept`s, she must also be qualified to teach `TypeVariance` (otherwise, she wouldn't be qualified to teach `ProgrammingConcept`s).

In other words, wherever we require a teacher who is qualified to teach `TypeVariance`, we can substitute a teacher who is qualified to teach `ProgrammingConcept`s in general

```
Teacher[ProgrammingConcept] <: Teacher[TypeVariance]
```

Since `TypeVariance` is _a kind of_ `ProgrammingConcept`, a `Teacher` of `ProgrammingConcept`s is a `Teacher` of `TypeVariance`.