Covariance, Invariance and Contravariance explained in plain English?
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Covariance, invariance, and contravariance are concepts often encountered in type theory and category theory, which are branches of mathematical logic and computer science. They are also commonly found in the design of programming languages, especially those that support object-oriented paradigms. Understanding these concepts helps in dealing with type hierarchies and polymorphism more effectively. Here, we’ll explore each term using simple examples and explanations.
Covariance
Covariance allows a function to accept a more specific type than initially defined. This concept is primarily useful when dealing with inheritance in object-oriented programming. When a data type is covariant, it can be replaced with its subtype without affecting the functioning of a program.
Example
Imagine you have a class hierarchy:
Suppose you have an array of animals:
Because arrays in Java are covariant, you can assign this array to an array of a subtype:
Covariance works well in scenarios where the type is only read and not modified. In a typical scenario, covariantly typed collections let you read items as objects of a more specific type.
Key Points
- Usage: When elements need to be read more than written.
- Example: Array types in Java, return types in method overriding.
Invariance
Invariance is the strictest of the three, meaning that a type cannot change at all. When a type system is invariant, a method or class can only work with exactly the type it was defined with, not its subtypes or supertypes.
Example
Consider a generic class in Java:
The line Box<Animal> animalBox = dogBox would result in a compilation error because generics in Java are invariant.
Key Points
- Usage: When type fidelity is crucial and both read and write operations are considered.
- Example: Most generic collections in programming languages like Java.
Contravariance
Contravariance allows a function to take arguments of more generic types than specified, enabling a function to handle inputs of varying types that are more general.
Example
Consider a scenario where you have several operations that work on animals:
In some languages, you might want to assign an Operation<Animal> to an Operation<Dog>. Contravariance allows functions to treat a Dog operation as an Animal operation.
Key Points
- Usage: When elements are predominantly input and methods are not returning values.
- Example: Method parameters in overriding, delegate types in C#.
Summary Table
| Feature | Covariance | Invariance | Contravariance |
| Definition | Read only with subtypes | No type change allowed | Accept more general types |
| Usage | Output-only contexts | Strict type assignment | Inputs or function arguments |
| Examples | Arrays, return types | Most generics | Method parameters |
| Programming Language Support | Java arrays, return types .NET delegates | Java generics, C# generics | C# method arguments
Java wildcard super |
Understanding these concepts is crucial when working with polymorphic code and can help prevent common pitfalls such as runtime exceptions and type errors. Covariance, invariance, and contravariance ensure that your code is flexible, safe, and logically sound. These principles provide you with the necessary tools to implement safer and more reusable code in typed languages.

