Generic type variance helps control how types relate when used with generics. It makes your code safer and more flexible by defining if types can be replaced by their subtypes or supertypes.
Generic type variance in Typescript
// TypeScript does not support 'in' or 'out' keywords like some languages // Covariance is expressed with readonly properties // Contravariance is expressed with function parameter types
TypeScript does not use explicit 'in' or 'out' keywords like some languages.
Covariance often happens with readonly properties, contravariance with function parameters.
interface ReadonlyArray<T> {
readonly [index: number]: T;
}type Handler<T> = (arg: T) => void; // Function parameters are contravariant in TypeScript
ReadonlyArray<Dog> where ReadonlyArray<Animal> is expected.function processAnimals(animals: ReadonlyArray<Animal>) {} const dogs: ReadonlyArray<Dog> = [{ name: 'Buddy' }]; processAnimals(dogs);
This example shows covariance: a ReadonlyArray<Dog> can be passed to a function expecting ReadonlyArray<Animal> safely.
class Animal { name: string; constructor(name: string) { this.name = name; } } class Dog extends Animal { bark() { console.log(`${this.name} says Woof!`); } } function feedAnimals(animals: ReadonlyArray<Animal>) { animals.forEach(animal => console.log(`Feeding ${animal.name}`)); } const dogs: ReadonlyArray<Dog> = [new Dog('Buddy'), new Dog('Max')]; feedAnimals(dogs);
Covariance means you can use a more specific type where a more general type is expected.
Contravariance means you can use a more general type where a more specific type is expected.
TypeScript infers variance mostly from how types are used (e.g., readonly properties for covariance).
Generic type variance controls how generic types relate to each other.
Covariance allows substituting a subtype for a supertype safely.
Contravariance allows substituting a supertype for a subtype safely.
Practice
Which of the following best describes covariance in TypeScript generic types?
Solution
Step 1: Understand covariance meaning
Covariance means you can use a more specific type (subtype) where a more general type (supertype) is expected.Step 2: Apply to generic types
In TypeScript, covariance allows substituting a subtype for a supertype safely in generics.Final Answer:
It allows a generic type to accept a subtype where a supertype is expected. -> Option BQuick Check:
Covariance = subtype for supertype [OK]
- Confusing covariance with contravariance
- Thinking covariance restricts substitution
- Assuming covariance means exact type only
Which of the following is the correct syntax to declare a generic function with contravariant parameter in TypeScript?
function process<T>(input: T): void { /* ... */ }Solution
Step 1: Recall TypeScript generic variance syntax
TypeScript does not useinoroutkeywords like some languages for variance.Step 2: Check valid generic function syntax
The correct syntax is simplyfunction process<T>(input: T): void { }without variance keywords.Final Answer:
function process<T>(input: T): void { } -> Option CQuick Check:
No variance keywords in TypeScript generics [OK]
- Using in/out keywords from other languages
- Trying to extend non-existent variance types
- Assuming variance is declared in generic syntax
Consider the following TypeScript code:
interface Animal { name: string; }
interface Dog extends Animal { bark(): void; }
function feedAnimals(animals: ReadonlyArray<Animal>) { /* ... */ }
const dogs: Dog[] = [{ name: 'Rex', bark: () => console.log('Woof!') }];
feedAnimals(dogs);What is the output or result of calling feedAnimals(dogs)?
Solution
Step 1: Understand ReadonlyArray variance
ReadonlyArray in TypeScript is covariant, so Dog[] can be used where ReadonlyArray<Animal> is expected.Step 2: Check function call validity
Passing dogs (Dog[]) to feedAnimals(animals: ReadonlyArray<Animal>) is allowed and compiles without error.Final Answer:
It compiles and runs without error because ReadonlyArray is covariant. -> Option DQuick Check:
ReadonlyArray is covariant = safe assignment [OK]
- Assuming arrays are invariant by default
- Confusing mutable arrays with ReadonlyArray
- Expecting runtime errors from type mismatch
Identify the error in the following TypeScript code related to generic variance:
interface Printer<T> {
print(value: T): void;
}
const animalPrinter: Printer<Animal> = {
print: (animal: Animal) => console.log(animal.name)
};
const dogPrinter: Printer<Dog> = animalPrinter;
dogPrinter.print({ name: 'Buddy', bark: () => console.log('Woof!') });Solution
Step 1: Analyze variance of Printer<T>
Printer<T> has a method accepting T as parameter, making it contravariant in T.Step 2: Check assignment direction
Assigning Printer<Animal> to Printer<Dog> is invalid because contravariance requires the opposite direction.Final Answer:
Assigning animalPrinter to dogPrinter causes a type error due to invariance. -> Option AQuick Check:
Function parameter generics are contravariant [OK]
- Ignoring contravariance in function parameters
- Assuming generic interfaces are always covariant
- Confusing parameter and return type variance
You want to create a generic event handler interface in TypeScript that accepts events of type T. The handler should be able to accept handlers for supertypes of T (contravariance). Which of the following interface declarations correctly models this behavior?
Solution
Step 1: Understand contravariance in function parameters
Function parameters are contravariant, so a function type like(event: T) => voidmodels contravariance naturally.Step 2: Compare interface and type alias
Interfaces with methods are invariant by default in TypeScript, but function types are contravariant in parameters, so using a type alias for a function is correct.Step 3: Check invalid syntax options
interface EventHandler<in T> { handle(event: T): void; } usesinkeyword which TypeScript does not support; interface EventHandler<T> { handle(event: T): T; } changes return type, losing contravariance.Final Answer:
type EventHandler<T> = (event: T) => void; -> Option AQuick Check:
Function types are contravariant in parameters [OK]
- Using unsupported 'in' keyword in generics
- Assuming interfaces are contravariant by default
- Changing return type affects variance incorrectly
Ready to prove it?
Pass the test and this pattern moves unknown
