union intersection and narrowing
Union Types Recap
A union type allows a variable to be one of several possible types, joined with a pipe |.
let id: string | number;
id = 101; // valid
id = "abc123"; // validUnion Types in Function Parameters
function printId(id: string | number): void {
console.log(`Your ID is: ${id}`);
}
printId(101);
printId("abc123");The Problem: Not All Methods Are Shared
function printId(id: string | number): void {
console.log(id.toUpperCase()); // Error: 'toUpperCase' does not exist on type 'number'
}Since id could be a number, and numbers don't have a toUpperCase method, TypeScript blocks this. This is where type narrowing comes in.
Narrowing with typeof
function printId(id: string | number): void {
if (typeof id === "string") {
console.log(id.toUpperCase()); // TypeScript knows id is a string here
} else {
console.log(id.toFixed(2)); // TypeScript knows id is a number here
}
}Narrowing with Array.isArray()
function printAll(value: string | string[]): void {
if (Array.isArray(value)) {
value.forEach(v => console.log(v));
} else {
console.log(value);
}
}Narrowing with in
The in operator checks whether a property exists on an object — useful for narrowing between different object shapes.
interface Cat {
meow(): void;
}
interface Dog {
bark(): void;
}
function makeSound(animal: Cat | Dog): void {
if ("meow" in animal) {
animal.meow();
} else {
animal.bark();
}
}Discriminated Unions
A powerful pattern where each type in a union shares a common literal property (a "tag") that identifies which variant it is.
interface Circle {
kind: "circle";
radius: number;
}
interface Square {
kind: "square";
sideLength: number;
}
type Shape = Circle | Square;
function getArea(shape: Shape): number {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
case "square":
return shape.sideLength ** 2;
}
}
getArea({ kind: "circle", radius: 5 }); // 78.53...
getArea({ kind: "square", sideLength: 4 }); // 16
Why this works well: TypeScript automatically narrowsshapeto the correct interface inside eachcase, based on the value of the sharedkindproperty.
Intersection Types
While a union means "one type OR another," an intersection (&) means "combine every type into one" — the result must satisfy all of them at once.
interface Person {
name: string;
}
interface Employee {
employeeId: number;
}
type StaffMember = Person & Employee;
const staff: StaffMember = {
name: "Alice",
employeeId: 1001
}; // must include properties from BOTH interfacesUnion vs Intersection — Quick Comparison
Union (|) | Intersection (&) |
|---|---|
| Value can be ONE of the listed types | Value must satisfy ALL listed types at once |
| Common for "either this or that" values | Common for merging multiple interfaces together |
Example: string | number | Example: Person & Employee |
Coming up next: We wrap up the course with utility types and a look at how TypeScript projects are configured.