<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged variance</title>
<link>https://coderlegion.com/tag/variance</link>
<description>Powered by Question2Answer</description>
<item>
<title>🧠 Understanding Variance in TypeScript &amp; Flow: Covariant, Contravariant, Invariant, Bivariant</title>
<link>https://coderlegion.com/22298/understanding-variance-typescript-flow-covariant-contravariant-invariant-bivariant</link>
<description>&lt;p&gt;Hi everyone!&lt;/p&gt;
&lt;p&gt;I’m &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/nyaomaru&quot;&gt;@nyaomaru&lt;/a&gt;, a frontend engineer who quietly moved to the Netherlands. 🇳🇱&lt;/p&gt;
&lt;p&gt;If you write &lt;code&gt;TypeScript&lt;/code&gt;, you’ve probably bumped into the term &lt;strong&gt;“variance”&lt;/strong&gt; at some point:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;covariant&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;contravariant&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;invariant&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;bivariant&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You may have a vague feeling of “I sorta get it… but not really.”&lt;/p&gt;
&lt;p&gt;Personally, I struggled especially with &lt;strong&gt;contravariance&lt;/strong&gt; and &lt;strong&gt;bivariance&lt;/strong&gt; — they’re really counter-intuitive.&lt;/p&gt;
&lt;p&gt;And when I tried to deep-read &lt;code&gt;React&lt;/code&gt;’s type definitions, I ran into &lt;code&gt;Flow&lt;/code&gt;’s variance annotations &lt;code&gt;+T&lt;/code&gt; / &lt;code&gt;-T&lt;/code&gt; and completely froze:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;export type Element&amp;lt;+C&amp;gt; = React$Element&amp;lt;C&amp;gt;;
export type RefSetter&amp;lt;-I&amp;gt; = React$RefSetter&amp;lt;I&amp;gt;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;“What are &lt;code&gt;+&lt;/code&gt; and &lt;code&gt;-&lt;/code&gt;!?”&lt;br&gt;
“Why is &lt;code&gt;React&lt;/code&gt; using this in &lt;code&gt;Flow&lt;/code&gt;!?”&lt;/p&gt;
&lt;p&gt;That was the &lt;strong&gt;entrance&lt;/strong&gt; to understand variance for me.&lt;/p&gt;
&lt;p&gt;In this article, I’ll use both &lt;code&gt;TypeScript&lt;/code&gt; and &lt;code&gt;Flow&lt;/code&gt; to build a &lt;strong&gt;practical&lt;/strong&gt;, &lt;strong&gt;real-world&lt;/strong&gt; understanding of variance:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You want to read &lt;code&gt;React’s&lt;/code&gt; type definitions without crying 😿&lt;/li&gt;
&lt;li&gt;You want to design safe callback types in &lt;code&gt;TypeScript&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;You want to avoid the bivariant foot-guns&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let’s dive in 👇&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;&lt;br&gt;
I won’t do a full &lt;code&gt;Flow&lt;/code&gt; tutorial here. I’ll only touch &lt;code&gt;Flow&lt;/code&gt; enough to explain how it expresses variance.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;-what-is-type-variance-&quot;&gt;🧩 What is type variance?&lt;/h3&gt;
&lt;p&gt;“Type variance” is about generic types and function types, and:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;how the subtype relationship between type parameters propagates to the outer type&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;For example, suppose &lt;code&gt;Cat&lt;/code&gt; is a subtype of &lt;code&gt;Animal&lt;/code&gt; (&lt;code&gt;Cat &amp;lt;: Animal&lt;/code&gt;):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Then is &lt;code&gt;List&amp;lt;Cat&amp;gt;&lt;/code&gt; also a subtype of &lt;code&gt;List&amp;lt;Animal&amp;gt;&lt;/code&gt;?&lt;/li&gt;
&lt;li&gt;Can we use &lt;code&gt;Handler&amp;lt;Animal&amp;gt;&lt;/code&gt; where a &lt;code&gt;Handler&amp;lt;Cat&amp;gt;&lt;/code&gt; is expected?&lt;/li&gt;
&lt;li&gt;What about something mutable like &lt;code&gt;Box&amp;lt;T&amp;gt;&lt;/code&gt;?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Variance is the set of rules that determines these “generic subtype” relationships.&lt;/p&gt;
&lt;p&gt;There are four basic flavors:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Covariant&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;Subtype relationship goes in the &lt;strong&gt;same direction&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Contravariant&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;Subtype relationship goes in the &lt;strong&gt;opposite direction&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Invariant&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;No subtype relationship either way&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bivariant&lt;/strong&gt;&lt;ul&gt;
&lt;li&gt;Both directions are allowed (&lt;code&gt;TypeScript&lt;/code&gt; foot-gun)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These are not specific to &lt;code&gt;TypeScript&lt;/code&gt; — you’ll find them in &lt;code&gt;Java&lt;/code&gt;, &lt;code&gt;C#&lt;/code&gt;, &lt;code&gt;Kotlin&lt;/code&gt;, &lt;code&gt;Flow&lt;/code&gt;, and pretty much any typed language that has generics.&lt;/p&gt;
&lt;p&gt;Let’s go through them one by one.&lt;/p&gt;
&lt;h4 id=&quot;-covariant-if-child-is-ok-using-it-as-parent-is-also-ok-&quot;&gt;⬆️ Covariant: “If child is OK, using it as parent is also OK”&lt;/h4&gt;
&lt;p&gt;Given &lt;code&gt;Cat &amp;lt;: Animal&lt;/code&gt;, covariance is:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Covariant: &lt;code&gt;F&amp;lt;Cat&amp;gt; &amp;lt;: F&amp;lt;Animal&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Roughly: a “container of more specific things” can be used where a “container of more general things” is expected.&lt;/p&gt;
&lt;p&gt;This is typically used for &lt;strong&gt;read-only&lt;/strong&gt; types.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;class Animal {
  name = 'animal';
}
class Cat extends Animal {
  meow() {}
}

type ReadonlyBox&amp;lt;T&amp;gt; = {
  readonly value: T;
};

const catBox: ReadonlyBox&amp;lt;Cat&amp;gt; = {
  value: new Cat(),
};

// Using &quot;box of Cat&quot; as &quot;box of Animal&quot; is fine
const animalBox: ReadonlyBox&amp;lt;Animal&amp;gt; = catBox;

// Reading as Animal is always safe
// animalBox.value.meow(); // Type error: Animal doesn't have meow()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here we only &lt;strong&gt;read&lt;/strong&gt; from the box, so it’s safe to treat a &lt;code&gt;ReadonlyBox&amp;lt;Cat&amp;gt;&lt;/code&gt; as a &lt;code&gt;ReadonlyBox&amp;lt;Animal&amp;gt;&lt;/code&gt;. That’s &lt;strong&gt;covariance&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ccipn9wpbmtyblauouf6.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Top: &lt;code&gt;Cat&lt;/code&gt; → &lt;code&gt;Animal&lt;/code&gt; (usual subtype relation)&lt;/li&gt;
&lt;li&gt;Bottom: &lt;code&gt;ReadonlyBox&amp;lt;Cat&amp;gt;&lt;/code&gt; → &lt;code&gt;ReadonlyBox&amp;lt;Animal&amp;gt;&lt;/code&gt; (same direction → covariant)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;-contravariant-if-parent-is-ok-you-can-use-it-in-a-child-only-slot-&quot;&gt;⬇️ Contravariant: “If parent is OK, you can use it in a child-only slot”&lt;/h4&gt;
&lt;p&gt;Again with &lt;code&gt;Cat &amp;lt;: Animal&lt;/code&gt;, contravariance is:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Contravariant: &lt;code&gt;F&amp;lt;Animal&amp;gt;&lt;/code&gt; &amp;lt;: &lt;code&gt;F&amp;lt;Cat&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The idea is:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;A function that can handle a wider type can safely be used wherever a narrower type handler is required.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This comes up with &lt;strong&gt;function parameter types&lt;/strong&gt; (i.e., “write-only” positions).&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;class Animal {
  name = 'animal';
}
class Cat extends Animal {
  meow() {}
}

type Handler&amp;lt;T&amp;gt; = (value: T) =&amp;gt; void;

const handleAnimal: Handler&amp;lt;Animal&amp;gt; = (animal: Animal) =&amp;gt; {
  console.log(animal.name);
};

const handleCat: Handler&amp;lt;Cat&amp;gt; = (cat: Cat) =&amp;gt; {
  cat.meow();
};

// Safe with contravariance:
// A handler that accepts any Animal can be used as a Cat-specific handler
const catHandler: Handler&amp;lt;Cat&amp;gt; = handleAnimal; // OK (in theory)

// The opposite is unsafe: a Cat-only handler
// cannot safely handle all Animals (Dog, Bird, ...)
// const animalHandler: Handler&amp;lt;Animal&amp;gt; = handleCat; // Should be an error
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;Handler&amp;lt;Animal&amp;gt;&lt;/code&gt; can handle any animal (including &lt;code&gt;Cat&lt;/code&gt;), so it’s safe to use where a “Cat handler” is expected.&lt;/p&gt;
&lt;p&gt;The reverse is &lt;strong&gt;not&lt;/strong&gt; safe → that’s contravariance.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9knw3k5y0a4tcht7qogr.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Top: &lt;code&gt;Cat&lt;/code&gt; → &lt;code&gt;Animal&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Bottom: &lt;code&gt;Handler&amp;lt;Animal&amp;gt;&lt;/code&gt; → &lt;code&gt;Handler&amp;lt;Cat&amp;gt;&lt;/code&gt; (reverse direction → contravariant)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;-invariant-no-subtyping-either-way-&quot;&gt;⛔ Invariant: “No subtyping either way”&lt;/h4&gt;
&lt;p&gt;Even if &lt;code&gt;Cat &amp;lt;: Animal&lt;/code&gt; , with invariance:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Invariant: &lt;code&gt;F&amp;lt;Cat&amp;gt;&lt;/code&gt; and &lt;code&gt;F&amp;lt;Animal&amp;gt;&lt;/code&gt; have &lt;strong&gt;no&lt;/strong&gt; subtype relation&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;class Animal {
  name = 'animal';
}
class Cat extends Animal {
  meow() {}
}

type Box&amp;lt;T&amp;gt; = {
  value: T;
};

let animalBox: Box&amp;lt;Animal&amp;gt; = { value: new Animal() };
let catBox: Box&amp;lt;Cat&amp;gt; = { value: new Cat() };

// Both of these are theoretically unsafe:
//
// animalBox = catBox;
// catBox = animalBox;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Why?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If you treat &lt;code&gt;Box&amp;lt;Cat&amp;gt;&lt;/code&gt; as &lt;code&gt;Box&amp;lt;Animal&amp;gt;&lt;/code&gt;:&lt;ul&gt;
&lt;li&gt;You could write a plain &lt;code&gt;Animal&lt;/code&gt; into it&lt;/li&gt;
&lt;li&gt;But someone else might assume it still only contains &lt;code&gt;Cat&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;If you treat &lt;code&gt;Box&amp;lt;Animal&amp;gt;&lt;/code&gt; as &lt;code&gt;Box&amp;lt;Cat&amp;gt;&lt;/code&gt;:&lt;ul&gt;
&lt;li&gt;You might pull an &lt;code&gt;Animal&lt;/code&gt; from it and assume it’s always a &lt;code&gt;Cat&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Since it’s &lt;strong&gt;mutable&lt;/strong&gt; and used for both read and write, the safe option is:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Make it invariant (no subtyping).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In pure type theory we’d reject both assignments.&lt;/p&gt;
&lt;p&gt;In practice, TypeScript treats most generics as approximately covariant, so &lt;code&gt;Box&amp;lt;Cat&amp;gt;&lt;/code&gt; → &lt;code&gt;Box&amp;lt;Animal&amp;gt;&lt;/code&gt; may compile — but conceptually, &lt;code&gt;Box&amp;lt;T&amp;gt;&lt;/code&gt; should be invariant.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t0ygcdcqyapn4zci25ro.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Top: &lt;code&gt;Cat&lt;/code&gt; → &lt;code&gt;Animal&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Bottom: no arrow between &lt;code&gt;Box&amp;lt;Cat&amp;gt;&lt;/code&gt; and &lt;code&gt;Box&amp;lt;Animal&amp;gt;&lt;/code&gt; → invariant&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;-bivariant-both-directions-are-ok-and-that-s-exactly-the-problem-typescript-s-hole&quot;&gt;🔁 Bivariant: “Both directions are OK (and that’s exactly the problem)” — TypeScript’s hole&lt;/h4&gt;
&lt;p&gt;Given &lt;code&gt;Cat &amp;lt;: Animal&lt;/code&gt;, bivariance is:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Bivariant: &lt;code&gt;F&amp;lt;Cat&amp;gt;&lt;/code&gt; and &lt;code&gt;F&amp;lt;Animal&amp;gt;&lt;/code&gt; are mutually assignable&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So it’s like “covariant + contravariant at the same time”.&lt;br&gt;
From a soundness standpoint, this is pretty much always a bad idea.&lt;/p&gt;
&lt;p&gt;But &lt;code&gt;TypeScript&lt;/code&gt; allows it in some cases (esp. some callback parameter types) for &lt;code&gt;JavaScript&lt;/code&gt; compatibility.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;type EventHandler&amp;lt;T&amp;gt; = (event: T) =&amp;gt; void;

declare let handleEvent: EventHandler&amp;lt;Event&amp;gt;;
declare let handleMouse: EventHandler&amp;lt;MouseEvent&amp;gt;;

// In sound type theory, only one of these would be allowed (depending on design).
// In TypeScript, *both* can be allowed in certain contexts (bivariant).
handleEvent = handleMouse; // OK in some cases (but can be unsafe)
handleMouse = handleEvent; // Also OK
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;MouseEvent&lt;/code&gt; is a subtype of &lt;code&gt;Event&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;If both directions are allowed, you can pass the “wrong” handler&lt;/li&gt;
&lt;li&gt;&lt;code&gt;TypeScript&lt;/code&gt; chooses practicality over strict safety here&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6f7xvofg602t5pi5539e.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Top: &lt;code&gt;MouseEvent&lt;/code&gt; → &lt;code&gt;Event&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Bottom: &lt;code&gt;Handler&amp;lt;MouseEvent&amp;gt;&lt;/code&gt; ⇔ &lt;code&gt;Handler&amp;lt;Event&amp;gt;&lt;/code&gt; → bivariant&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;-quick-summary-of-the-four&quot;&gt;🎯 Quick summary of the four&lt;/h4&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Kind          &lt;/th&gt;&lt;th&gt;Direction       &lt;/th&gt;&lt;th&gt;Safety           &lt;/th&gt;&lt;th&gt;Typical example&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Covariant     &lt;/td&gt;&lt;td&gt;Child → Parent  &lt;/td&gt;&lt;td&gt;Safe for reads   &lt;/td&gt;&lt;td&gt;&lt;code&gt;readonly T[]&lt;/code&gt;, &lt;code&gt;ReadonlyArray&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Contravariant &lt;/td&gt;&lt;td&gt;Parent → Child  &lt;/td&gt;&lt;td&gt;Safe for writes  &lt;/td&gt;&lt;td&gt;&lt;code&gt;(arg: T) =&amp;gt; void&lt;/code&gt;, handlers&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Invariant     &lt;/td&gt;&lt;td&gt;No subtyping    &lt;/td&gt;&lt;td&gt;Safe for mutable &lt;/td&gt;&lt;td&gt;&lt;code&gt;Box&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Bivariant     &lt;/td&gt;&lt;td&gt;Both directions &lt;/td&gt;&lt;td&gt;Unsound / risky  &lt;/td&gt;&lt;td&gt;Some TS callback parameter positions&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h3 id=&quot;-typescript-s-reality-vs-pure-type-theory&quot;&gt;⚙️ &lt;code&gt;TypeScript&lt;/code&gt;’s reality vs. “pure” type theory&lt;/h3&gt;
&lt;p&gt;So far we’ve been in the “beautiful, clean theory world”:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Read-only → &lt;strong&gt;covariant&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Write-only (function parameters) → &lt;strong&gt;contravariant&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Mutable read + write → &lt;strong&gt;invariant&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Both directions → &lt;strong&gt;unsound (bivariant)&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But &lt;code&gt;TypeScript&lt;/code&gt; does not fully implement this ideal.&lt;/p&gt;
&lt;p&gt;Because of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Historical &lt;code&gt;JavaScript&lt;/code&gt; APIs&lt;/li&gt;
&lt;li&gt;Massive existing &lt;code&gt;JavaScript&lt;/code&gt; codebases&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Browser&lt;/code&gt; / &lt;code&gt;DOM&lt;/code&gt; APIs design&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;TypeScript&lt;/code&gt; makes several pragmatic compromises:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;T[]&lt;/code&gt; arrays are &lt;strong&gt;treated as covariant&lt;/strong&gt;, even though they should be invariant&lt;/li&gt;
&lt;li&gt;Function parameter positions are &lt;strong&gt;often bivariant&lt;/strong&gt;, not strict contravariant&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This mismatch between &lt;strong&gt;type theory intuition&lt;/strong&gt; and &lt;strong&gt;TS behavior&lt;/strong&gt; is where a lot of confusion comes from.&lt;/p&gt;
&lt;p&gt;Let’s look at the two big ones.&lt;/p&gt;
&lt;h4 id=&quot;arrays-should-be-invariant-but-ts-treats-them-as-basically-covariant-&quot;&gt;Arrays: should be invariant, but TS treats them as “basically covariant”&lt;/h4&gt;
&lt;p&gt;Formally:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;code&gt;T[]&lt;/code&gt; should really be &lt;strong&gt;invariant&lt;/strong&gt;&lt;br&gt;
but &lt;code&gt;TypeScript&lt;/code&gt; treats it as if it were &lt;strong&gt;covariant&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Which means some unsafe code compiles.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;class Animal {
  name = 'animal';
}
class Cat extends Animal {
  meow() {}
}
class Dog extends Animal {
  bark() {}
}

const cats: Cat[] = [new Cat()];

// TS treats Cat[] as a subtype of Animal[]
const animals: Animal[] = cats;

// Now this is allowed:
animals.push(new Dog());

// But the underlying array is still &quot;cats&quot;
const cat: Cat = cats[1]; // Type says Cat, but it's actually a Dog
cat.meow(); // Possible runtime error
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Why does TS allow this?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;From a type theory perspective, &lt;code&gt;T[]&lt;/code&gt; is a mutable container → should be invariant&lt;/li&gt;
&lt;li&gt;But &lt;code&gt;JavaScript&lt;/code&gt;’s arrays are extremely flexible and historically treated loosely&lt;/li&gt;
&lt;li&gt;Making &lt;code&gt;T[]&lt;/code&gt; strictly invariant would break a lot of existing &lt;code&gt;JavaScript&lt;/code&gt; code&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So &lt;code&gt;TypeScript&lt;/code&gt; chose to keep &lt;code&gt;T[]&lt;/code&gt; almost &lt;strong&gt;covariant&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The recommended alternative is &lt;strong&gt;read-only arrays&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;const cats: readonly Cat[] = [new Cat()];
const animals: readonly Animal[] = cats; // Safe
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Because we only read from a &lt;code&gt;readonly&lt;/code&gt; array, it can be safely covariant.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;In practice: most generics in TS behave “mostly covariant”.&lt;br&gt;
The real danger is specifically “mutable but treated as covariant”, like &lt;code&gt;T[]&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id=&quot;function-parameters-should-be-contravariant-but-often-behave-bivariantly&quot;&gt;Function parameters: should be contravariant, but often behave bivariantly&lt;/h4&gt;
&lt;p&gt;The second big compromise: &lt;strong&gt;function parameter variance&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;In theory:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Function &lt;strong&gt;parameter&lt;/strong&gt; positions should be &lt;strong&gt;contravariant&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In practice, TypeScript often treats them as &lt;strong&gt;bivariant&lt;/strong&gt;, especially in:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Methods in object types&lt;/li&gt;
&lt;li&gt;Some callback positions&lt;/li&gt;
&lt;li&gt;When &lt;code&gt;strictFunctionTypes&lt;/code&gt; is disabled&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let’s see why that’s a problem.&lt;/p&gt;
&lt;h4 id=&quot;why-should-parameters-be-contravariant-&quot;&gt;Why should parameters be contravariant?&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;type Handler&amp;lt;T&amp;gt; = (value: T) =&amp;gt; void;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here, &lt;code&gt;T&lt;/code&gt; is used in &lt;strong&gt;parameter position&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It’s on the “receiving data” side&lt;/li&gt;
&lt;li&gt;Which means it should be &lt;strong&gt;contravariant&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If &lt;code&gt;Cat &amp;lt;: Animal&lt;/code&gt;, then (in theory):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;Handler&amp;lt;Animal&amp;gt; &amp;lt;: Handler&amp;lt;Cat&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We’ve already seen this pattern.&lt;/p&gt;
&lt;h4 id=&quot;but-ts-sometimes-allows-both-directions-bivariant-&quot;&gt;But TS sometimes allows both directions (bivariant)&lt;/h4&gt;
&lt;p&gt;To maintain compatibility with existing &lt;code&gt;JavaScript&lt;/code&gt;, TS allows both directions in many callback cases:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;type Handler&amp;lt;T&amp;gt; = (value: T) =&amp;gt; void;

declare let handleEvent: Handler&amp;lt;Event&amp;gt;;
declare let handleMouse: Handler&amp;lt;MouseEvent&amp;gt;;

// In some contexts, TS allows:
handleEvent = handleMouse; // OK
handleMouse = handleEvent; // OK
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;&lt;p&gt;With a purely sound type system, one of these would be rejected.&lt;br&gt;
TS chooses convenience over strict safety.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Consider this more dangerous version:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;type Handler&amp;lt;T&amp;gt; = (value: T) =&amp;gt; void;

const handleEvent: Handler&amp;lt;Event&amp;gt; = (event) =&amp;gt; {
  console.log(event.type);
};

const handleMouse: Handler&amp;lt;MouseEvent&amp;gt; = (event) =&amp;gt; {
  // MouseEvent-specific API
  console.log(event.clientX);
};

// Assign MouseEvent handler to a generic Event handler
const eventHandler: Handler&amp;lt;Event&amp;gt; = handleMouse;

// This is allowed by the type system:
eventHandler(new Event('click')); // Runtime error: no clientX
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This &lt;strong&gt;should&lt;/strong&gt; be a type error, but bivariant behavior lets it through.&lt;/p&gt;
&lt;h4 id=&quot;why-did-the-ts-team-do-this-&quot;&gt;Why did the TS team do this?&lt;/h4&gt;
&lt;p&gt;Because real-world &lt;code&gt;JavaScript&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Doesn’t assume strict contravariance&lt;/li&gt;
&lt;li&gt;Has tons of “loosely typed” callback APIs (&lt;code&gt;DOM&lt;/code&gt;, &lt;code&gt;Node.js&lt;/code&gt;, libraries, …)&lt;/li&gt;
&lt;li&gt;Would break massively if TS suddenly enforced full contravariance&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So the TS team made a pragmatic choice:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Prioritize developer experience and compatibility&lt;br&gt;
over 100% soundness.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id=&quot;what-about-strictfunctiontypes-&quot;&gt;What about &lt;code&gt;strictFunctionTypes&lt;/code&gt;?&lt;/h4&gt;
&lt;p&gt;There is a partial escape hatch:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;compilerOptions&quot;: {
    &quot;strict&quot;: true,
    &quot;strictFunctionTypes&quot;: true
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With &lt;code&gt;strictFunctionTypes: true&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Standalone function types&lt;/strong&gt; are treated more contravariantly&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Methods in object types&lt;/strong&gt; are still treated more loosely (bivariantly) for compatibility&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So even in strict mode, you don’t get “perfect” contravariance — but you get closer.&lt;/p&gt;
&lt;h4 id=&quot;-takeaway-typescript-is-not-a-pure-variance-lab-&quot;&gt;🎯 Takeaway: &lt;code&gt;TypeScript&lt;/code&gt; is not a “pure variance lab”&lt;/h4&gt;
&lt;p&gt;To summarize:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Arrays &lt;code&gt;T[]&lt;/code&gt; should be invariant → TS treats them as almost covariant&lt;/li&gt;
&lt;li&gt;Function parameters should be contravariant → TS often treats them as bivariant&lt;/li&gt;
&lt;li&gt;&lt;code&gt;strictFunctionTypes&lt;/code&gt; helps, but doesn’t give you fully sound variance&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So &lt;code&gt;TypeScript&lt;/code&gt; is:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;not a perfectly sound type theory playground,&lt;br&gt;
but a pragmatic type system sitting on top of messy real-world JavaScript.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Variance in TS is “good enough” most of the time, but you should be aware of where it leaks.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;-how-flow-expresses-variance-explicitly&quot;&gt;🌊 How Flow expresses variance explicitly&lt;/h3&gt;
&lt;p&gt;Quick recap: &lt;code&gt;Flow&lt;/code&gt; is a static type checker for &lt;code&gt;JavaScript&lt;/code&gt;, originally developed at &lt;code&gt;Facebook&lt;/code&gt; (now &lt;code&gt;Meta&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Key characteristics:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;More &lt;strong&gt;strict&lt;/strong&gt; and &lt;strong&gt;soundness-oriented&lt;/strong&gt; than TS&lt;/li&gt;
&lt;li&gt;Variance is &lt;strong&gt;explicitly&lt;/strong&gt; annotated&lt;/li&gt;
&lt;li&gt;Strong type inference&lt;/li&gt;
&lt;li&gt;Type annotations layered onto plain JS&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In very rough terms:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;code&gt;TypeScript&lt;/code&gt; focuses on practicality&lt;br&gt;
&lt;code&gt;Flow&lt;/code&gt; focuses more on safety&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;One of Flow’s signature features is &lt;strong&gt;explicit variance annotations&lt;/strong&gt;.&lt;/p&gt;
&lt;h4 id=&quot;variance-annotations-in-flow&quot;&gt;Variance annotations in Flow&lt;/h4&gt;
&lt;p&gt;In &lt;code&gt;Flow&lt;/code&gt;, you write variance directly on type parameters:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Syntax &lt;/th&gt;&lt;th&gt;Meaning&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;+T&lt;/code&gt;   &lt;/td&gt;&lt;td&gt;&lt;strong&gt;covariant&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;-T&lt;/code&gt;   &lt;/td&gt;&lt;td&gt;&lt;strong&gt;contravariant&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;T&lt;/code&gt;    &lt;/td&gt;&lt;td&gt;&lt;strong&gt;invariant&lt;/strong&gt;(no sign)&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;So in &lt;code&gt;Flow&lt;/code&gt;, the author of a type explicitly declares:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;“This type parameter is used covariantly / contravariantly / invariantly.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In &lt;code&gt;TypeScript&lt;/code&gt;, the compiler mostly infers this.&lt;br&gt;
In &lt;code&gt;Flow&lt;/code&gt;, you annotate it, and Flow checks that your usage matches.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;-how-react-uses-variance-in-flow-types&quot;&gt;⚛️ How React uses variance in Flow types&lt;/h3&gt;
&lt;p&gt;React’s internal types were historically written in Flow, and you can still see traces of it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;export type Element&amp;lt;+C&amp;gt; = React$Element&amp;lt;C&amp;gt;;
export type RefSetter&amp;lt;-I&amp;gt; = React$RefSetter&amp;lt;I&amp;gt;;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;+C&lt;/code&gt; is covariant&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-I&lt;/code&gt; is contravariant&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;why-is-element-lt-c-gt-covariant-&quot;&gt;Why is Element&amp;lt;+C&amp;gt; covariant?&lt;/h4&gt;
&lt;p&gt;Because &lt;code&gt;ReactElement&lt;/code&gt; is essentially &lt;strong&gt;immutable&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You construct it&lt;/li&gt;
&lt;li&gt;You read from it&lt;/li&gt;
&lt;li&gt;You don’t mutate its props in place&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So it’s safe to say that if &lt;code&gt;CatProps &amp;lt;: AnimalProps&lt;/code&gt;, then:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;code&gt;ReactElement&amp;lt;CatProps&amp;gt; &amp;lt;: ReactElement&amp;lt;AnimalProps&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;type AnimalProps = { name: string };
type CatProps = { name: string; meow: () =&amp;gt; void };

function Cat(props: CatProps) {
  return null as any;
}

const catElement: ReactElement&amp;lt;CatProps&amp;gt; = (
  &amp;lt;Cat name=&quot;nyaomaru&quot; meow={() =&amp;gt; {}} /&amp;gt;
);

// Because of covariance, this is safe:
const parent: ReactElement&amp;lt;AnimalProps&amp;gt; = catElement;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;A component that accepts “more specific props” can be used where “more general props” are expected&lt;/li&gt;
&lt;li&gt;The element is read-only — we’re not mutating its props through this type&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;why-is-refsetter-lt--i-gt-contravariant-&quot;&gt;Why is RefSetter&amp;lt;-I&amp;gt; contravariant?&lt;/h4&gt;
&lt;p&gt;Because &lt;code&gt;RefSetter&lt;/code&gt; &lt;strong&gt;receives&lt;/strong&gt; values (instances) — it doesn’t produce them:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It’s on the “write side”&lt;/li&gt;
&lt;li&gt;That’s a &lt;strong&gt;contravariant&lt;/strong&gt; position&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;type HTMLDivRef = (el: HTMLDivElement | null) =&amp;gt; void;
type HTMLElementRef = (el: HTMLElement | null) =&amp;gt; void;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We have &lt;code&gt;HTMLDivElement &amp;lt;: HTMLElement&lt;/code&gt;, and with contravariance:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;RefSetter&amp;lt;HTMLElement&amp;gt; &amp;lt;: RefSetter&amp;lt;HTMLDivElement&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A callback that can handle any &lt;code&gt;HTMLElement&lt;/code&gt; can safely be used where a “div-only” ref setter is expected&lt;/li&gt;
&lt;li&gt;But the opposite is unsafe: a &lt;code&gt;HTMLDivElement&lt;/code&gt;-only ref setter cannot safely accept any &lt;code&gt;HTMLElement&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This matches exactly our earlier &lt;code&gt;Handler&amp;lt;T&amp;gt;&lt;/code&gt; examples.&lt;/p&gt;
&lt;h4 id=&quot;why-this-matters-for-reading-react-s-types&quot;&gt;Why this matters for reading React’s types&lt;/h4&gt;
&lt;p&gt;Once you understand &lt;code&gt;Flow&lt;/code&gt;’s variance annotations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;+C &lt;/code&gt;(covariant) on &lt;code&gt;ReactElement&amp;lt;+C&amp;gt;&lt;/code&gt; → safe, immutable, read-only&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-T&lt;/code&gt; (contravariant) on &lt;code&gt;RefSetter&amp;lt;-T&amp;gt;&lt;/code&gt; → callback parameter, write-only&lt;/li&gt;
&lt;li&gt;No sign → invariant types where mutation may happen&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This makes React’s Flow types much easier to reason about.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;-where-variance-actually-matters-in-real-code&quot;&gt;🛠️ Where variance actually matters in real code&lt;/h3&gt;
&lt;p&gt;This may still feel theoretical, but it absolutely shows up in day-to-day work:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;onClick&lt;/code&gt;, &lt;code&gt;onChange&lt;/code&gt;, &lt;code&gt;onSubmit&lt;/code&gt; → UI event handlers&lt;/li&gt;
&lt;li&gt;&lt;code&gt;onSuccess&lt;/code&gt;, &lt;code&gt;onError&lt;/code&gt; → async/API callbacks&lt;/li&gt;
&lt;li&gt;Exposed “handler” or “listener” APIs in your own libraries&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All of these are basically:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;type Handler&amp;lt;T&amp;gt; = (value: T) =&amp;gt; void;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Parameter types → &lt;strong&gt;contravariant&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Return types → &lt;strong&gt;covariant&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Mutable containers → &lt;strong&gt;invariant&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;TS callback parameters in methods → often &lt;strong&gt;bivariant&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When designing public APIs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Prefer&lt;/strong&gt; wider types for parameters your consumers pass in&lt;/li&gt;
&lt;li&gt;Avoid exposing raw mutable containers like &lt;code&gt;Box&amp;lt;T&amp;gt;&lt;/code&gt; when a &lt;code&gt;ReadonlyBox&amp;lt;T&amp;gt;&lt;/code&gt; will do&lt;/li&gt;
&lt;li&gt;Consider using &lt;code&gt;readonly&lt;/code&gt; arrays and properties when you can&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In other words:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Variance is a mental checklist for API design, not something you only care about in textbooks.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;-wrap-up&quot;&gt;📌 Wrap-up&lt;/h3&gt;
&lt;p&gt;We covered a lot, so let’s distill the main points:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Variance determines how subtyping of type parameters affects the outer type:&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Covariant&lt;/strong&gt; → same direction (usually read-only)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Contravariant&lt;/strong&gt; → opposite direction (usually function parameters)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Invariant&lt;/strong&gt; → no subtyping either way (mutable containers)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bivariant&lt;/strong&gt; → both directions (convenient but unsound)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;TypeScript:&lt;ul&gt;
&lt;li&gt;Treats many generics as “mostly covariant”&lt;/li&gt;
&lt;li&gt;Makes &lt;code&gt;T[]&lt;/code&gt; effectively covariant (even though it should be invariant)&lt;/li&gt;
&lt;li&gt;Often treats function parameters as bivariant&lt;/li&gt;
&lt;li&gt;Provides &lt;code&gt;strictFunctionTypes&lt;/code&gt; to tighten some of this&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Flow:&lt;ul&gt;
&lt;li&gt;Has explicit variance annotations (&lt;code&gt;+T&lt;/code&gt;, &lt;code&gt;-T&lt;/code&gt;, &lt;code&gt;T&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Practically:&lt;ul&gt;
&lt;li&gt;&lt;code&gt;readonly&lt;/code&gt; vs mutable is a variance decision&lt;/li&gt;
&lt;li&gt;Callback parameter types are a variance decision&lt;/li&gt;
&lt;li&gt;Whether you expose &lt;code&gt;Box&amp;lt;T&amp;gt;&lt;/code&gt; or &lt;code&gt;ReadonlyBox&amp;lt;T&amp;gt;&lt;/code&gt; is a variance decision&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You don’t need to memorize all the formal rules,&lt;br&gt;
but keeping “covariant / contravariant / invariant / bivariant” in your mental toolbox makes both &lt;strong&gt;reading&lt;/strong&gt; library types and &lt;strong&gt;designing&lt;/strong&gt; your own APIs much easier.&lt;/p&gt;
&lt;p&gt;I also made a small repo where you can play with these variance patterns in TypeScript:&lt;/p&gt;
&lt;p&gt;👉 &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/nyaomaru/variance-check&quot;&gt;https://github.com/nyaomaru/variance-check&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/024tr0vk866y6rm3bkea.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;Have a nice variance life 🐈⬛✨&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;references&quot;&gt;References&lt;/h3&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://en.wikipedia.org/wiki/Type_variance&quot;&gt;https://en.wikipedia.org/wiki/Type_variance&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.typescriptlang.org/docs/handbook/2/generics.html#variance-annotations&quot;&gt;https://www.typescriptlang.org/docs/handbook/2/generics.html#variance-annotations&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.totaltypescript.com/method-shorthand-syntax-considered-harmful&quot;&gt;https://www.totaltypescript.com/method-shorthand-syntax-considered-harmful&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.sandromaglione.com/articles/covariant-contravariant-and-invariant-in-typescript&quot;&gt;https://www.sandromaglione.com/articles/covariant-contravariant-and-invariant-in-typescript&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://zenn.dev/jay_es/articles/2024-02-13-typescript-variance&quot;&gt;https://zenn.dev/jay_es/articles/2024-02-13-typescript-variance&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://typescriptbook.jp/reference/generics/variance&quot;&gt;https://typescriptbook.jp/reference/generics/variance&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://effectivetypescript.com/2021/05/06/unsoundness/&quot;&gt;https://effectivetypescript.com/2021/05/06/unsoundness/&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;-shameless-plug&quot;&gt;🐾 Shameless plug&lt;/h3&gt;
&lt;p&gt;When you write &lt;code&gt;TypeScript&lt;/code&gt;, you probably end up writing &lt;code&gt;isXXX&lt;/code&gt; guards over and over.&lt;br&gt;
It’s boring and error-prone, so I built a small OSS library to help:&lt;/p&gt;
&lt;p&gt;👉 &lt;code&gt;is-kit&lt;/code&gt;: a tiny toolkit for building composable, type-safe type guards&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kd71eb31m2x63vh1pzey.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/nyaomaru/is-kit&quot;&gt;https://github.com/nyaomaru/is-kit&lt;/a&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/22298/understanding-variance-typescript-flow-covariant-contravariant-invariant-bivariant</guid>
<pubDate>Sat, 11 Jul 2026 12:20:44 +0000</pubDate>
</item>
</channel>
</rss>