<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged flow</title>
<link>https://coderlegion.com/tag/flow</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>
<item>
<title>Work Is Water: Beat Developer Infinity By Paying Attention to Flow</title>
<link>https://coderlegion.com/6246/work-is-water-beat-developer-infinity-by-paying-attention-to-flow</link>
<description>&lt;p&gt;This article is for everyone drowning in work. If your to-do list seems infinite and you’re feeling the burnout, you’ll find out how to think like a river and embrace a manageable flow.&lt;/p&gt;
&lt;h3 id=&quot;the-flow-of-work&quot;&gt;The Flow of Work&lt;/h3&gt;
&lt;p&gt;We are busy &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.theguardian.com/environment/2025/may/15/record-number-river-blocking-dams-removed-europe&quot;&gt;removing dams, weirs, culverts, and sluices&lt;/a&gt; because we’ve learned that nature has a better way to manage water. Natural waterways slow the water’s course through the landscape, reducing peak water levels, reducing flooding risk and providing more preparation time when a flood cannot be prevented.&lt;/p&gt;
&lt;p&gt;In contrast, human-made watercourses rush the water downstream, where it converges and creates dramatic flooding. If you think about it, this sounds exactly like how we manage work in software teams and beyond. To be productive as an individual, we crush our tasks and rush them downstream.&lt;/p&gt;
&lt;p&gt;If we were a river, there would be constant flooding in the lowlands. But there’s a catch that makes it even worse. Our work resists gravity and flows in all directions. It often flows back to us, like when clear our emails only to trigger a new set of responses. That’s why we live knee-deep, waist-deep, or neck-deep in water all the time.&lt;/p&gt;
&lt;p&gt;The wicked part of &lt;a rel=&quot;nofollow&quot; href=&quot;https://moduscooperandi.com/books-and-classes&quot;&gt;drowning in work&lt;/a&gt; is that our water never reaches the sea. The more we bail out, the deeper we get. When you start so much work, it takes forever to finish it. In fact, the more time you consume in switching tasks, rebuilding task context, and responding to the overheads of high work-in-process (WIP), the less time you spend completing stuff.&lt;/p&gt;
&lt;p&gt;It’s time to reconnect with nature and discover that you must slow down to go fast.&lt;/p&gt;
&lt;h3 id=&quot;slow-is-smooth-smooth-is-fast&quot;&gt;Slow is Smooth, Smooth is Fast&lt;/h3&gt;
&lt;p&gt;I worked for a company in the financial industry on a software project with a fixed timeline set by regulators. With a long list of work to do, we summoned the developer hustle. We started early, worked through lunch, and stayed late, only to see our work move to the next stage and wait.&lt;/p&gt;
&lt;p&gt;Developing changes faster made no difference to the timelines, and the more we rushed, the more frequently the work came back. Overall, we were making the project take longer.&lt;/p&gt;
&lt;p&gt;Intention, mindfulness, and precision will always get you to your outcome faster than pure hustle. Pausing after a task to plan your next move feels like wasting time you could have spent on “real work”. But stumbling ever onward to complete a to-do list that is effectively infinite sounds like the worst kind of Sisyphean endeavor. And it is.&lt;/p&gt;
&lt;p&gt;The moment of clarity in the modern workplace is when you accept that the queue of work can never be completed. We face infinite choices and the tasks that each decision may generate. You cannot possibly do it all. Ever.&lt;/p&gt;
&lt;p&gt;Many people respond to massive demand by working extra hours, rushing individual tasks, or hiring additional help. This doesn’t work because doubling, tripling, or 10-x-ing your capacity makes no dent in infinity.&lt;/p&gt;
&lt;p&gt;Instead, we need to set the rate at which infinity can enter our work system. We slow down the water, dry our feet, and establish a healthy flow of work.&lt;/p&gt;
&lt;h3 id=&quot;working-is-drinking&quot;&gt;Working is Drinking&lt;/h3&gt;
&lt;p&gt;When I get thirsty, I drink a cup of water. Admittedly, it’s often a less noble choice than water, but it’s still a single cup at a time. You can’t survive by drinking 90 liters once per month.&lt;/p&gt;
&lt;p&gt;Planning is the same. We need to do it continuously because the need to choose between competing demands is ever-present. Planning after each task may be too much, but planning once a week is likely too little.&lt;/p&gt;
&lt;p&gt;There are many ways to manage this. I use a combination of Pomodoro Technique, Personal Kanban and Getting Things Done. This helps me look at the work at different planning horizons and levels of detail. You don’t have to use these, but you need something that lets you continuously plan and work with intention.&lt;/p&gt;
&lt;p&gt;When planning, the question isn’t how you do all the work. The crucial activity is deciding what you won’t work on. This is hard, as it contains most of this infinite list of options. It’s a law of nature that choosing to do one thing is choosing not to do something else. Don’t fight it.&lt;/p&gt;
&lt;p&gt;When you perform continuous small-scale planning, it doesn’t take so much time. It also gives you a hook for crucial conversations with people trying to wash work downstream to you. There’s great power in asking, “Is this more important than these items I have planned?” You’ll often find it’s not.&lt;/p&gt;
&lt;h3 id=&quot;how-to-slow-the-water&quot;&gt;How To Slow The Water&lt;/h3&gt;
&lt;p&gt;We slow the water by having courageous conversations. Sometimes, we must tell people we can’t accept a request because we’re already committed to other things. On other occasions, we collaborate with them to understand priorities.&lt;/p&gt;
&lt;p&gt;When I ran software teams, I allowed stakeholders to re-prioritize the upcoming work as often as they liked. Once developers started a work item, we let them complete it. You may use a different default, but this prevents the waste caused by “half a feature”.&lt;/p&gt;
&lt;p&gt;This doesn’t just apply to development. Imagine you had a plan to write a book, get it published, and then do promotional book tours, television chat shows, and social media posts to promote it.&lt;/p&gt;
&lt;p&gt;Imagine you wrote the book, published it, and didn’t do the promotional work. You may have gotten distracted by your next book idea, so you’ve started writing it and don’t have time to promote the original book. You’ll end up with many books and no sales when it’s better to have one best-seller than many flops.&lt;/p&gt;
&lt;p&gt;The Lean movement puts it this way: Stop starting and start finishing.&lt;/p&gt;
&lt;h3 id=&quot;be-intentional&quot;&gt;Be Intentional&lt;/h3&gt;
&lt;p&gt;Every single person in knowledge work has an infinite to-do list. Only by being intentional can we slow down the flow of work and have a greater impact. It’s easy to fall into a completionist mindset where you believe that applying more effort will somehow get you closer to the finish line, but this is a shared delusion.&lt;/p&gt;
&lt;p&gt;You can’t ever be done, but you can choose to work where you can make the biggest impact.&lt;/p&gt;
</description>
<category>Time Management</category>
<guid isPermaLink="true">https://coderlegion.com/6246/work-is-water-beat-developer-infinity-by-paying-attention-to-flow</guid>
<pubDate>Thu, 09 Oct 2025 13:48:55 +0000</pubDate>
</item>
</channel>
</rss>