typescript / expert
Snippet
Variance Annotations with in/out Modifiers
TypeScript 4.7 introduced explicit variance annotations 'in' and 'out' for generic type parameters. 'out T' marks a covariant position (the type only appears in outputs), 'in T' marks contravariance (only inputs), and 'in out' enforces invariance. These annotations are validated by the compiler: if you mark a parameter 'out' but use it in an input position, TypeScript will error. Beyond documentation, they speed up assignability checks because the compiler can short-circuit variance computation. Use them on interfaces whose generic parameter has a fixed directional role to make subtyping intent explicit and catch accidental mis-uses.
snippet.ts
typescript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
interface Producer<out T> {produce(): T;}interface Consumer<in T> {consume(value: T): void;}declare const animalProducer: Producer<{ name: string }>;// Covariant: a Producer<NarrowerOutput> flows into Producer<WiderOutput>const detailedProducer: Producer<{ name: string; age?: number }> = animalProducer;declare const stringConsumer: Consumer<string>;// Contravariant: a Consumer<WiderInput> flows into Consumer<NarrowerInput>const literalConsumer: Consumer<"hello"> = stringConsumer;
Breakdown
1
interface Producer<out T> {
'out' declares T as covariant — Producer<Sub> is assignable to Producer<Super>.
2
interface Consumer<in T> {
'in' declares T as contravariant — Consumer<Super> is assignable to Consumer<Sub>.
3
const detailedProducer: Producer<{ name: string; age?: number }> = animalProducer;
Works because covariance lets a producer of a narrower shape satisfy a producer of a wider shape.
4
const literalConsumer: Consumer<"hello"> = stringConsumer;
Works because contravariance lets a consumer of the wider 'string' accept the narrower literal 'hello'.