# Covariance and contravariance (computer science)

**Covariance and contravariance** are properties of type constructors, such as generic interfaces, delegates, functions, and arrays, that describe how subtyping of their type arguments translates into subtyping of the constructed types. A constructor `F<T>` is covariant over `T` if `T` being a subtype of `U` implies `F<T>` is a subtype of `F`; it is contravariant if the implication runs in the reverse direction, so `F` is a subtype of `F<T>`; and it is invariant if no subtyping relation can be derived between the constructed types.<sup>[1](https://doc.rust-lang.org/reference/subtyping.html)</sup> In C# terms, covariance preserves assignment compatibility and contravariance reverses it.<sup>[2](https://learn.microsoft.com/dotnet/csharp/programming-guide/concepts/covariance-contravariance/)</sup>

| Key fact | Detail |
|---|---|
| Definitions | `F<T>` covariant over `T`: `T <: U` implies `F<T> <: F`; contravariant: `T <: U` implies `F <: F<T>`; invariant: neither.<sup>[1](https://doc.rust-lang.org/reference/subtyping.html)</sup> |
| Canonical example | A function type is covariant in its return type and contravariant in its argument type.<sup>[3](https://www.cs.cornell.edu/courses/cs3110/2020sp/manual-4.8/polymorphism.html)</sup> |
| C# support | Delegate variance since C# 2.0 (2005); generic interface and delegate variance since C# 4.0 (April 2010); covariant return types since C# 9.<sup>[4](https://learn.microsoft.com/dotnet/csharp/whats-new/csharp-version-history)</sup><sup> • </sup><sup>[5](https://learn.microsoft.com/en-us/dotnet/standard/generics/covariance-and-contravariance)</sup> |
| Java approach | Generic types are invariant; use-site wildcards (`? extends`, `? super`) create co- and contravariant relationships.<sup>[6](https://kotlinlang.org/docs/generics.html)</sup><sup> • </sup><sup>[7](https://docs.oracle.com/javase/tutorial/java/generics/subtyping.html)</sup> |
| Kotlin and Scala | Declaration-site variance with `out`/`in` (Kotlin) and `+`/`-` annotations (Scala); Kotlin arrays are invariant.<sup>[6](https://kotlinlang.org/docs/generics.html)</sup><sup> • </sup><sup>[8](https://docs.scala-lang.org/scala3/book/types-variance.html)</sup> |
| Known unsoundness | Java's covariant arrays require a run-time store check and can throw `ArrayStoreException`; Eiffel's covariant method overriding also required run-time checking.<sup>[9](https://download.java.net/java/early_access/jdk26/docs/specs/jls/jls-10.html)</sup><sup> • </sup><sup>[10](https://www.cs.cornell.edu/courses/cs4120/2022sp/notes/subtyping/)</sup> |

## The function-type rule

The clearest instance of both directions is the function type. A function of type `τ1 → τ2` is a subtype of `τ1' → τ2'` when `τ1' ≤ τ1` and `τ2 ≤ τ2'`: the argument position reverses the subtyping direction (contravariance) and the result position preserves it (covariance).<sup>[10](https://www.cs.cornell.edu/courses/cs4120/2022sp/notes/subtyping/)</sup> The intuition is that a function that accepts a broader argument type and returns a narrower result type can be used anywhere a function with narrower inputs and broader outputs is expected.<sup>[10](https://www.cs.cornell.edu/courses/cs4120/2022sp/notes/subtyping/)</sup> Scala makes this explicit in its library: `trait Function[-A, +B]` marks the argument type contravariant and the result type covariant.<sup>[8](https://docs.scala-lang.org/scala3/book/types-variance.html)</sup>

Mutable state breaks both directions. In OCaml, a type variable appearing in a position describing mutable state becomes invariant; if a value of type `x ref` could be coerced to `xy ref`, one could store a `Y` inside it and later read it as an `x`, breaking the type system.<sup>[3](https://www.cs.cornell.edu/courses/cs3110/2020sp/manual-4.8/polymorphism.html)</sup> Rust follows the same principle: `&'a T` is covariant in both parameters, but `&'a mut T` is invariant in `T`, and `std::cell::UnsafeCell<T>` is invariant.<sup>[1](https://doc.rust-lang.org/reference/subtyping.html)</sup>

## Language mechanisms

**Declaration-site variance.** C#, Scala, and Kotlin let the author of a generic type mark each type parameter. In .NET, variance is a property of a generic interface or delegate type's type parameter and controls which implicit reference conversions exist between constructed types; by default generic type parameters are invariant, so `List<Derived>` and `List<Base>` are unrelated.<sup>[5](https://learn.microsoft.com/en-us/dotnet/standard/generics/covariance-and-contravariance)</sup> A covariant parameter can be used as a return type, and a contravariant parameter as a parameter type; for example, `IEnumerable<Derived>` can be assigned to `IEnumerable<Base>`, and `Action<Base>` to `Action<Derived>`.<sup>[5](https://learn.microsoft.com/en-us/dotnet/standard/generics/covariance-and-contravariance)</sup> Scala uses `+` and `-` prefixes on type parameters, with no annotation meaning invariant.<sup>[11](https://docs.scala-lang.org/tour/variances.html)</sup> Kotlin's `out` marks a parameter that is only produced and `in` one that is only consumed, and Kotlin does not allow specifying both co- and contravariance on the same parameter.<sup>[6](https://kotlinlang.org/docs/generics.html)</sup><sup> • </sup><sup>[12](https://kotlinlang.org/spec/pdf/sections/type-system.pdf)</sup>

**Use-site variance.** Java takes the other approach: generic classes and interfaces are invariant, and wildcards at each use create the relationship. An upper-bounded wildcard `? extends T` gives covariant access and a lower-bounded wildcard `? super T` gives contravariant access; the Java tutorial's guideline is that "in" variables (data sources) use `extends` and "out" variables (data sinks) use `super`, and a variable used both ways should not use a wildcard.<sup>[7](https://docs.oracle.com/javase/tutorial/java/generics/subtyping.html)</sup><sup> • </sup><sup>[13](https://docs.oracle.com/javase/tutorial/java/generics/wildcardGuidelines.html)</sup> The PLDI 2011 paper *Taming the Wildcards* summarizes the trade-off: definition-site variance, as in Scala and C#, offers simple type-instantiation rules but fractures definitions of naturally invariant classes, while use-site variance, as in Java, keeps class definitions simple but produces type-instantiation rules that elude most programmers.<sup>[14](https://www.researchgate.net/publication/220752051_Taming_the_Wildcards_Combining_Definition-_and_Use-Site_Variance)</sup>

**Rust.** In Rust, all subtyping is tied to lifetimes, and variance describes how the subtyping of a generic parameter affects the subtyping of the type. The compiler infers variance from use by fixed-point iteration over a lattice with bivariant at the top and invariant at the bottom, always able to fall back on declaring every parameter invariant.<sup>[1](https://doc.rust-lang.org/reference/subtyping.html)</sup><sup> • </sup><sup>[15](https://rustc-dev-guide.rust-lang.org/variance.html)</sup> The only source of contravariance in the language is the arguments to a function, so it rarely arises in practice.<sup>[16](https://doc.rust-lang.org/nomicon/subtyping.html)</sup>

## Soundness and the array problem

A covariant subtyping rule for arrays is unsound in a language with mutable arrays. Given `TA[] ≤ Person[]`, code can write a `Person` through the `Person[]` alias into an array actually holding `TA` objects, and a later read as `TA` fails at run time; the sound rule makes array types invariant.<sup>[10](https://www.cs.cornell.edu/courses/cs4120/2022sp/notes/subtyping/)</sup> Java nonetheless made reference arrays covariant for compatibility with pre-generics code, and pays for it with a run-time store check: an assignment to a component of an array whose type is `A[]` is checked at run time, and an `ArrayStoreException` is thrown when the value is not assignable to the component.<sup>[9](https://download.java.net/java/early_access/jdk26/docs/specs/jls/jls-10.html)</sup> The Java generics design explicitly made type safety a primary goal, forbidding array creation with parameterized element types because such arrays would compile without warnings yet fail at run time.<sup>[17](https://docs.oracle.com/javase/tutorial/extra/generics/fineprint.html)</sup>

Method overriding shows the same tension. The function-subtyping rule permits covariant refinement of return types and contravariant generalization of argument types in subclasses. Java permits the covariant part: from Java SE 5.0 onwards, an overriding method may declare a more derived return type.<sup>[18](https://docs.oracle.com/javase/specs/jls/se10/html/jls-8.html)</sup> It does not permit contravariant argument generalization, probably because it would interact poorly with Java's overloading mechanism.<sup>[10](https://www.cs.cornell.edu/courses/cs4120/2022sp/notes/subtyping/)</sup> Eiffel instead used the covariant rule for conformance checking, which is unsound, and run-time type checking had to be added later to make the language type-safe.<sup>[10](https://www.cs.cornell.edu/courses/cs4120/2022sp/notes/subtyping/)</sup>

## History and theory

The modern framing comes from Giuseppe Castagna, a researcher at CNRS and Université Paris Cité, whose 1995 article "Covariance and contravariance: conflict without a cause" (*ACM Transactions on Programming Languages and Systems*, 17(3):431–447) argued that the two relations characterize distinct and independent mechanisms: the contravariance rule correctly captures the subtyping relation, which establishes which sets of functions can replace another in every context, while a covariant relation characterizes the specialization of code, the definition of new code replacing old definitions in particular cases.<sup>[19](https://www.researchgate.net/publication/220404197_Covariance_and_Contravariance_Conflict_without_a_Cause)</sup> He concluded that covariance and contravariance are not opposing views but distinct concepts that both can and should be type-safely integrated in an object-oriented language.<sup>[20](https://www.di.ens.fr/reports/1996/liens-94-18.A4.pdf)</sup> In a 2018 reappraisal, Castagna revisited that result in light of semantic subtyping, arguing that types should be conceived first and the language designed to fit them.<sup>[21](https://arxiv.org/abs/1809.01427)</sup>

Language support arrived in stages. C# 2.0 (November 2005) listed covariance and contravariance among its features, applying to delegates and method groups; C# 4.0 (April 2010) extended the feature to generic type parameters of interfaces and delegates, letting developers define their own variant types.<sup>[4](https://learn.microsoft.com/dotnet/csharp/whats-new/csharp-version-history)</sup><sup> • </sup><sup>[22](https://learn.microsoft.com/en-us/archive/msdn-magazine/2010/july/csharp-4-0-new-csharp-features-in-the-net-framework-4)</sup> In the .NET runtime, variant type parameters are restricted to generic interface and delegate types, a single interface or delegate can mix covariant and contravariant parameters, variance applies only to reference types (a value-type argument makes the parameter invariant for that constructed type), and variance does not apply to delegate combination, since delegates combine only when their types match exactly.<sup>[5](https://learn.microsoft.com/en-us/dotnet/standard/generics/covariance-and-contravariance)</sup>

Java's wildcards remain theoretically demanding. Subtyping for Java wildcards is suspected to be undecidable because wildcards are a form of bounded [existential types](https://www.edgepedia.org/existential-type), and some wildcard types have no joins, complicating type-argument inference.<sup>[23](https://yanniss.github.io/variance-pldi11.pdf)</sup> The Java Language Specification notes that wildcards are a restricted form of existential types.<sup>[24](https://cr.openjdk.org/~dlsmith/jep401/jep401-20260225/specs/jls/jls-4.html)</sup>

## References

1. [Subtyping and variance, The Rust Reference](https://doc.rust-lang.org/reference/subtyping.html)
2. [Covariance and Contravariance (C#), Microsoft Learn](https://learn.microsoft.com/dotnet/csharp/programming-guide/concepts/covariance-contravariance/)
3. [Chapter 5, Polymorphism and its limitations, CS 3110 manual, Cornell](https://www.cs.cornell.edu/courses/cs3110/2020sp/manual-4.8/polymorphism.html)
4. [The history of C#, Microsoft Learn](https://learn.microsoft.com/dotnet/csharp/whats-new/csharp-version-history)
5. [Covariance and Contravariance in Generics, .NET, Microsoft Learn](https://learn.microsoft.com/en-us/dotnet/standard/generics/covariance-and-contravariance)
6. [Generics: in, out, where, Kotlin Documentation](https://kotlinlang.org/docs/generics.html)
7. [Wildcards and Subtyping, The Java Tutorials, Oracle](https://docs.oracle.com/javase/tutorial/java/generics/subtyping.html)
8. [Variance, Scala 3 Book, Scala Documentation](https://docs.scala-lang.org/scala3/book/types-variance.html)
9. [Chapter 10, Arrays, Java Language Specification (JDK 26 draft)](https://download.java.net/java/early_access/jdk26/docs/specs/jls/jls-10.html)
10. [Subtyping notes, CS 4120, Cornell](https://www.cs.cornell.edu/courses/cs4120/2022sp/notes/subtyping/)
11. [Variances, Tour of Scala, Scala Documentation](https://docs.scala-lang.org/tour/variances.html)
12. [Kotlin language specification, type system sections](https://kotlinlang.org/spec/pdf/sections/type-system.pdf)
13. [Guidelines for Wildcard Use, The Java Tutorials, Oracle](https://docs.oracle.com/javase/tutorial/java/generics/wildcardGuidelines.html)
14. [Taming the Wildcards: Combining Definition- and Use-Site Variance (PLDI 2011)](https://www.researchgate.net/publication/220752051_Taming_the_Wildcards_Combining_Definition-_and_Use-Site_Variance)
15. [Variance, Rust Compiler Development Guide](https://rustc-dev-guide.rust-lang.org/variance.html)
16. [Subtyping and Variance, The Rustonomicon](https://doc.rust-lang.org/nomicon/subtyping.html)
17. [The Fine Print, The Java Tutorials, Oracle](https://docs.oracle.com/javase/tutorial/extra/generics/fineprint.html)
18. [Chapter 8, Classes, Java Language Specification, Java SE 10](https://docs.oracle.com/javase/specs/jls/se10/html/jls-8.html)
19. [Giuseppe Castagna (1995). Covariance and Contravariance: Conflict without a Cause, ACM TOPLAS 17(3):431–447](https://www.researchgate.net/publication/220404197_Covariance_and_Contravariance_Conflict_without_a_Cause)
20. [LIENS technical report 94-18, École Normale Supérieure](https://www.di.ens.fr/reports/1996/liens-94-18.A4.pdf)
21. [Giuseppe Castagna (2018). Covariance and Contravariance: a fresh look at an old issue, arXiv:1809.01427](https://arxiv.org/abs/1809.01427)
22. [C# 4.0 – New C# Features in the .NET Framework 4, MSDN Magazine](https://learn.microsoft.com/en-us/archive/msdn-magazine/2010/july/csharp-4-0-new-csharp-features-in-the-net-framework-4)
23. [Taming Wildcards in Java's Type System (PLDI 2011)](https://yanniss.github.io/variance-pldi11.pdf)
24. [Chapter 4, Types, Values, and Variables, Java Language Specification](https://cr.openjdk.org/~dlsmith/jep401/jep401-20260225/specs/jls/jls-4.html)

---
*Topic: Encyclopedia › Physical world and mathematics › Mathematics and statistics › Logic and discrete mathematics › Formal logic and foundations › Logical calculi and logical syntax › Lambda calculus and type theory*

*Initially written Sep 17, 2026 · Reviewed: Oct 15, 2026 · Edited: Oct 15, 2026 · Last review: Oct 15, 2026*

*Copyright 2026 EdgeChat AI, a subsidiary of Biostate AI.*

License: Edgepedia Community License 1.0, https://www.edgepedia.org/edgepedia/license
