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
Ais a subtype ofB(A <: B), thenF(A)is a subtype ofF(B).- In TypeScript: This is primarily seen in "producer" positions. For example, an array of
Dog(Dog[]) is a subtype ofAnimal[]because the elements being read out are guaranteed to be at leastAnimal. In function types, the return type is covariant. If a function returns aDog, it can safely be used where a function returning anAnimalis expected.
- In TypeScript: This is primarily seen in "producer" positions. For example, an array of
-
Contravariance (Reversing Direction): A type relationship is contravariant if it reverses the ordering of types. If
A <: B, thenF(B)is a subtype ofF(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 acceptsObject(because a function capable of handling anyObjectcan certainly handle aDog). However, you cannot pass a function that specifically requires aDogto a caller expecting anAnimalhandler, as this would violate type safety when aCatis passed.
- In TypeScript: This occurs in "consumer" positions. The most critical application is in function arguments. If a function expects an argument of type
-
Invariance (No Relationship): A type is invariant if
F(A)is only a subtype ofF(B)ifAandBare 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 aCatinto anAnimalarray; if it were contravariant, you could read aCatfrom anObjectreference. To prevent runtime errors during mutation, TypeScript enforces invariance on mutable generic members.
- 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
-
Application in Interface Design: When designing interfaces, developers must decide the variance of generic parameters. Using
in(contravariant) andout(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