Files
2nd/01_Archive/2026-04-20/Variance (Covariance, Contravariance, Invariance).md

3.4 KiB

Variance (Covariance, Contravariance, Invariance) 📌 Brief Summary Variance describes how the subtyping relationship between complex types (such as functions or generics) relates to the subtyping relationship of their component types. In TypeScript's type system, understanding variance is critical for designing safe interfaces, as it determines whether a subtype can be substituted for a supertype without violating type safety.

📖 Core Content In the context of TypeScript interface design, variance defines the rules for "substitutability" (Liskov Substitution Principle) when dealing with generic parameters or function signatures.

  • Covariance (Preserving Direction): A type relationship is covariant if it preserves the ordering of types. If A is a subtype of B (A <: B), then F(A) is a subtype of F(B).

    • In TypeScript: This is primarily seen in "producer" positions. For example, an array of Dog (Dog[]) is a subtype of Animal[] because the elements being read out are guaranteed to be at least Animal. In function types, the return type is covariant. If a function returns a Dog, it can safely be used where a function returning an Animal is expected.
  • Contravariance (Reversing Direction): A type relationship is contravariant if it reverses the ordering of types. If A <: B, then F(B) is a subtype of F(A).

    • In TypeScript: This occurs in "consumer" positions. The most critical application is in function arguments. If a function expects an argument of type Animal, you can safely pass it a function that accepts Object (because a function capable of handling any Object can certainly handle a Dog). However, you cannot pass a function that specifically requires a Dog to a caller expecting an Animal handler, as this would violate type safety when a Cat is passed.
  • Invariance (No Relationship): A type is invariant if F(A) is only a subtype of F(B) if A and B are identical. There is no subtyping relationship allowed between the generic containers, regardless of the relationship between the underlying types.

    • In TypeScript: This occurs when a type acts as both a producer and a consumer (e.g., a mutable property in an interface). If you have a property value: T, it must be invariant. If it were covariant, you could write a Cat into an Animal array; if it were contravariant, you could read a Cat from an Object reference. To prevent runtime errors during mutation, TypeScript enforces invariance on mutable generic members.
  • Application in Interface Design: When designing interfaces, developers must decide the variance of generic parameters. Using in (contravariant) and out (covariant) annotations (though primarily a feature of languages like C# or Scala, the logic applies to how TypeScript infers compatibility) allows for more flexible and reusable API designs.

🔗 Knowledge Connections

  • Related Topics: Liskov-Substitution-Principle, Function Type Compatibility
  • Projects/Contexts: TypeScript Structural Type System
  • Contradictions/Notes: Note that TypeScript's type system is "structural," meaning variance is determined by the shape of the object rather than explicit declarations, which can sometimes lead to unexpected compatibility in complex nested generics.

Last updated: 2026-04-17