WalzoneInterview Prep
📞 Interviewing soon? Practice with a realistic AI mock phone interview — it calls you, then scores you. First 15 min FREE →

Scala · Intermediate · question 40 of 100

Explain the concept of variance in Scala, specifically covariant and contravariant types.?

📕 Buy this interview preparation book: 100 Scala questions & answers — PDF + EPUB for $5

In Scala, variance refers to the concept of subtyping relationships between generic types that are parameterized by other types. It helps to define how the subtyping relationship between the base types will affect the derived types. Variance can be classified into three types:

1. Covariant (denoted by +)

2. Contravariant (denoted by -)

3. Invariant (no annotations)

### Covariant Types:

Covariance means that if ‘A‘ is a subtype of ‘B‘, then ‘F[A]‘ is a subtype of ‘F[B]‘. In Scala, you can denote covariance by putting a ‘+‘ before the type parameter, like this:

trait List[+A] {
  // ...
}

A real-life example could be the relationship between animals and mammals. If ‘Animal‘ is the supertype and ‘Mammal‘ is the subtype, then if ‘List[Mammal]‘ is a subtype of ‘List[Animal]‘, it would be considered covariant.

For example, a simple implementation of the List trait could look like this:

sealed trait List[+A]
case object Nil extends List[Nothing]
case class Cons[A](head: A, tail: List[A]) extends List[A]

Here, the list is covariant, which allows us to do the following:

val mammals: List[Mammal] = /* ... */
val animals: List[Animal] = mammals

As we can see, we assigned a list of ‘Mammal‘ to a list of ‘Animal‘ because list is covariant in its type parameter.

### Contravariant Types:

Contravariance means that if ‘A‘ is a subtype of ‘B‘, then ‘F[B]‘ is a subtype of ‘F[A]‘. In Scala, you can denote contravariance by putting a ‘-‘ before the type parameter, like this:

trait Function1[-A, +B] {
  def apply(a: A): B
}

Taking the animal example again, if ‘Animal‘ is the supertype and ‘Mammal‘ is the subtype, then if ‘F[Animal]‘ is a subtype of ‘F[Mammal]‘, it would be considered contravariant.

For example, here’s the implementation of the Function1 trait:

trait Function1[-A, +B] {
 def apply(a: A): B
}

With contravariance, the following is allowed:

val animalFeeder: Function1[Animal, Food] = /* ... */
val mammalFeeder: Function1[Mammal, Food] = animalFeeder

As you can see, we assigned a ‘Function1[Animal, Food]‘ to a ‘Function1[Mammal, Food]‘. The ‘-A‘ in the type parameter means the function is contravariant in its input parameter.

### Invariant Types:

When there is no subtyping relationship between ‘F[A]‘ and ‘F[B]‘ for all ‘A‘ and ‘B‘, we call the types invariant. In Scala, invariance is the default behavior, and you don’t need to put any annotations for the type parameter.

For example:

trait MyBox[A]

In this case, the ‘MyBox‘ type is invariant, meaning there is no subtyping relationship between ‘MyBox[A]‘ and ‘MyBox[B]‘, even if ‘A‘ is a subtype of ‘B‘.

To summarize, covariance, contravariance, and invariance are ways to describe how parametrized types relate to each other when subtyping is involved. Scala allows us to explicitly define these relationships with annotations like ‘+‘ and ‘-‘ in type parameters, giving us greater flexibility when working with more complex object hierarchies.

Reading is step one. Saying it out loud is the interview. Our AI interviewer calls your phone and runs a realistic Scala interview — then scores it.
📞 Practice Scala — free 15 min
📕 Buy this interview preparation book: 100 Scala questions & answers — PDF + EPUB for $5

All 100 Scala questions · All topics