in / out Variance (Covariance & Contravariance)
Last updated: 2026-10-22
in / out Variance (Covariance & Contravariance)
Variance describes how a type parameter inside a composite type affects overall compatibility: returns are covariant (subtypes substitute), parameters are contravariant (functions accepting supertypes substitute). Function assignability and nested interface compatibility all follow it — once understood, "why does this assignment error or not" has a systematic answer.
| Group | Generics |
|---|---|
| Kind | Syntax |
| Strict mode | Not required |
ℹ️ Info The editor transpiles TypeScript to JavaScript and runs it (no type checking); see each example's Output for type-check results.
📝 Syntax
// TS 4.7+ allows explicit variance annotations (inferred structurally by default)
interface Producer<out T> { produce(): T; } // covariant: only produces T
interface Consumer<in T> { consume(v: T): void; } // contravariant: only consumes T
// The natural rules of function assignment
let handleDog: (d: Dog) => void = handleAnimal; // OK: parameter contravariance
const adoptAnimal: (tag: string) => Animal = adoptDog; // OK: return covariance
⚙️ Parameters
| <out T> | T appears only in output positions (returns): Producer<Dog> can serve as Producer<Animal> |
|---|---|
| <in T> | T appears only in input positions (parameters): Consumer<Animal> can serve as Consumer<Dog> |
| <in out T> | Read and write both exposed: strictly identical types required, otherwise incompatible |
Returns:Variance is a compatibility rule with no runtime code; it decides only "what may be assigned to what".
▶ Example
Edit the code below and click Try it to see the transpiled result:
Output:
Rex eats food
Rex-A1 eats food
(Runtime output after transpilation; the reverse assignment — using a Dog-only handler as a universal one — is rejected by the compiler)
💡 Notes
ℹ️ Info Intuition: "outputs may be more specific (co), consumers may be broader (contra)" — a pet owner who accepts any Animal can handle a Dog; a kennel producing Dogs supplies any Animal need.
⚠️ Note Mutable arrays both produce and consume (bivariant) — the classic hole for unsafe assignments; when designing generic interfaces, decide whether T flows in or out, then annotate in/out.
💡 Tip TS 4.7 explicit annotations are mostly documentation and speed: structural inference is usually right, but in/out makes deep checks faster, intent clearer, and error messages plainer.
🏷️ Related Generics
💡 Related cases
More cases coming soon.
📚 Related tutorials
❓ FAQ
Q Covariance vs contravariance in one sentence?
A Look at where the type parameter appears: output positions (returns) are covariant — subtypes stay compatible; input positions (parameters) are contravariant — supertypes stay compatible. Both in and out means invariant (strictly identical).
Q Why are function parameters contravariant, not covariant?
A Counterexample: if (d: Dog) => void could fill (a: Animal) => void, a caller passing Cat would break. But (a: Animal) => void filling (d: Dog) => void is safe — it handles any Animal, Dog included.
Q When do you need explicit in/out annotations?
A When parameter positions are complex, structural inference is slow, or results feel counterintuitive (event emitters, deeply nested interfaces). Day-to-day code can lean on inference — no need to annotate everywhere.