36.7 Variance Annotations in TypeScript 4.7+: in and out

Right, so you’ve finally decided to get serious about type safety. Good. You’ve probably built a generic class or two, maybe a Box<T> or a Repository<T>, and thought, “This is fine.” And for a while, it is. Then you try to assign a Box<string> to a Box<string | number> and TypeScript screams at you. You stare at the error, a vein throbbing gently in your forehead, because obviously a box of strings should be assignable to a box of strings-or-numbers. What could possibly go wrong?

36.6 The ThisType<T> Marker Interface

Right, so you’ve built a class hierarchy. Maybe a Vehicle base class with a drive() method, and then a Car and Boat that extend it. Now you want to add a pilot() method, but you want the return type of pilot() to be the specific class instance, not the base class. You want myCar.pilot() to return a Car, not a Vehicle, for that sweet, sweet chainable API. This is where ThisType<T> comes in—it’s TypeScript’s slightly arcane way of giving you a self-reference in the type system without you having to constantly type this.

36.5 Function Overloads: Multiple Signatures for a Single Implementation

Alright, let’s talk about function overloads, the TypeScript feature that lets you wear multiple hats without changing your head. You know how in JavaScript, a single function can return different types of things based on what you pass in? Think of parseInt(string) versus parseInt(string, radix). Overloads are how we teach TypeScript to understand that same flexibility, giving a single implementation multiple, precise type signatures. The core idea is simple: you list the different ways someone can call your function, and then you write one implementation that handles all of them. The secret sauce, and the part everyone screws up at least once, is that the implementation’s signature must be compatible with all the declared overloads. It’s like a bouncer at a club; the implementation has to be ready to handle anyone the overloads let in.

36.4 Covariance, Contravariance, and Invariance in TypeScript

Right, let’s talk about variance. It sounds like a terrifying term from category theory, but I promise you, it’s just a precise way to describe how type relationships “flow” when you start nesting types inside other types, like generics. It answers a simple question: If Dog is a subtype of Animal, is Array<Dog> a subtype of Array<Animal>? Your gut might say “yes,” and in TypeScript, for arrays, you’d be right. But that’s not a universal truth, and getting it wrong is how you end up with runtime errors that the type system was supposed to prevent. Let’s break down why.

36.3 Building a Simple Type-Level State Machine

Right, so you want to build a state machine. But not just any state machine—one that’s enforced at compile time. The kind where if you try to do something stupid, the compiler gives you a friendly, and by friendly I mean utterly incomprehensible, type error instead of letting your program explode at runtime. This is where type-level programming stops being academic and starts being your best friend. We’re going to build a simple state machine for a network connection. It has three states: Disconnected, Connecting, and Connected. The rules are simple:

36.2 Type-Level Arithmetic: Counting with Tuples

Right, so you want to do arithmetic at the type level. You’re probably thinking, “Why on earth would I need that?” Trust me, I had the same thought. The answer, as it so often is in TypeScript, is: because you’ve painted yourself into a very specific corner and this is the only way out. We’re not building general-purpose calculators here. This is about proving things to the type system—like the exact length of a tuple, or that you’ve split an array into two chunks of precisely equal size. It’s a parlor trick, but a damn useful one when you need it.

36.1 Recursive Types: Deeply Nested Structures

Alright, buckle up. We’re about to dive into one of TypeScript’s most powerful and, let’s be honest, initially mind-bending features: recursive types. You know how in JavaScript you can have an object that has a property which is itself an object, and so on, ad infinitum? Think of a comment thread, a filesystem, or a company org chart. We’re going to teach TypeScript’s type system to understand those deeply nested structures. It’s less like giving it instructions and more like giving it a map of a fractal. It just gets it.

— joke —

...