6.6 The Pitfalls of Intersecting Primitives

Alright, let’s talk about one of the first places TypeScript’s type system will gleefully smack you in the face: intersecting primitives. You’ve probably seen the & operator and thought, “Ah, union is OR, so intersection must be AND. I’ll take this string and this number and get a type that is both a string AND a number! Checkmate, type system!” Let me stop you right there. I need you to imagine the TypeScript compiler, a profoundly logical but utterly humorless entity, staring at you blankly. It’s not going to create a new quantum value that is simultaneously a string and a number. That’s not how our universe works, and it’s certainly not how TypeScript’s type algebra works.

6.5 Practical Union Patterns: Result Types and Either Types

Let’s be honest: most of the time, your functions will succeed. But the few times they don’t, you want to know why they failed, not just that they did. Relying on throwing errors is like communicating with a sledgehammer—it’s effective but lacks nuance and forces a try/catch block everywhere. Returning null or undefined is even worse; it’s a silent, data-less failure that you have to guess about. This is where Result types (or Either types) come in. They are the civilized, type-safe way to handle operations that can fail. Instead of blowing up the execution flow, they return a container that explicitly says, “Hey, here’s the successful output, OR here’s the detailed reason it went sideways.” The compiler then forces you to handle both possibilities. It’s not a new idea—languages like Rust and Haskell have baked this in for years—but it’s a pattern we can beautifully implement with TypeScript’s union types.

6.4 Distributing Unions Over Generics

Now, let’s talk about something that feels like a magic trick the first time you see it: how TypeScript distributes union types over generics. This isn’t just academic; it’s the secret sauce that makes your conditional types and mapped types work predictably with unions. It’s also the reason you sometimes get results that make you scratch your head and mutter, “Well, that’s not what I meant.” Here’s the core idea. When you have a generic type T that is a union (e.g., T extends A | B), and you use it in a naked type parameter context (more on that in a second), TypeScript doesn’t just evaluate the whole union at once. Instead, it says, “Okay, let’s take this union apart, apply the operation to each constituent individually, and then smash the results back together into a new union.” This is called distributive conditional types.

6.3 Narrowing Union Types: The Full Picture

Alright, let’s get our hands dirty with narrowing union types. This isn’t just some academic exercise; it’s the fundamental way you, the developer, prove to the TypeScript compiler that a value of a fuzzy type like string | number is, at this specific moment, definitely a string. You’re giving the compiler a logical proof, and it will reward you with access to the full API of that specific type. If you don’t do this, TypeScript will only let you use methods common to all members of the union, which is often… nothing useful.

6.2 Intersection Types: A & B for Composition

Alright, let’s talk about intersection types. You’ve just seen how union types (A | B) are about giving you options. Intersection types (A & B), on the other hand, are about forcing you to have everything. It’s the type system’s way of saying, “You must be this tall and have this mustache to ride this ride.” Think of an intersection type as a mash-up. If you have type A & B, you’re creating a new type that must have all the properties of A and all the properties of B. It’s composition. It’s telling you that for an object to be valid here, it needs to satisfy the contract of A and the contract of B simultaneously.

6.1 Union Types: A | B and Their Use Cases

Alright, let’s talk about union types. You know that feeling when you’re writing a function and you think, “This could take a string… or maybe a number. Ugh, I’ll just use any and hope for the best.” Stop that. You’re better than any. Union types are your first, best line of defense against that particular brand of laziness. They’re the type system’s way of saying, “It can be this, or that. I’m flexible, but I’m not a pushover.”

— joke —

...