TS Fun

While I was building out my portfolio, I discovered a funny quirk with Typescript generics.

A Quick Primer

For the uninitiated, a generic is simply a component (function, class, variable, etc) that spans types. You'll commonly see them in polymorphic functions. The contrived example we'll be using is pulled directly from the TS docs:

function identity<Type>(arg: Type): Type {
  return arg;
}

I've always read this from left-to-right, which lead me to believe that Type needs to be declared if arg is going to be casted correctly.

In my mind, there were 2 exceptions, which it turns out, are only partially true. The exceptions were:

  1. a constraint is declared with the extends keyword
function identityElement<Type extends HTMLElement>(arg: Type): Type {
  return arg;
}
  1. a default value is declared with the = operator:
function identityElement<Type = HTMLDivElement>(arg: Type): Type {
  return arg;
}

Enter "Type Argument Inference"

So there I was click-clacking along, when I absent-mindedly forgot to include an argument to the generic and yet, the correct type was returned. This wild phenomenon is known as type argument interference. Let me explain:

What's happening is TS is correctly inferring the type of the functional argument, which it assigns to Type. So in reality, the following return the same types

var a = identity<string>("");
var b = identity("");

And More!

Even cooler is that it respects the type differences between var, let and const. As you know, the const declarator means a variable immutable and therefore, its type is typically its value:

/**
 * This is of type `string` since it's mutable.
 * TS correctly infers it's a string and therefor any mutations will not change its type.
 */
var a = "hello";

/**
 * This, however, is of type `"goodbye` since it's immutable.
 */
const b = "goodbye";

This same inference is applied to generics:

/** The following are all type `"hello"` since they're immutable */

const a = identity("hello");
const hello = "hello!";
const results5 = identity(hello);

/** While these are all type `string` since they're mutable */

var goodby = "goodbye!";
const b = identity(goodby);
const goodbyeForGood = "GOODBYE!";
var c = identity(goodbyeForGood);

Application

If you want to see this in action, you can checkout the source code to the site you're on right now: