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.

GroupGenerics
KindSyntax
Strict modeNot 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.
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.
Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏