TypeScript Lesson 9: Type Narrowing
Type narrowing is how TypeScript figures out a more specific type within a code block. This is what makes union types actually usable.
typeof Narrowing
function format(value: string | number | boolean): string {
if (typeof value === "string") {
return value.toUpperCase(); // TypeScript knows it's string here
} else if (typeof value === "number") {
return value.toFixed(2); // TypeScript knows it's number here
} else {
return value ? "Yes" : "No"; // TypeScript knows it's boolean here
}
}
instanceof and in Narrowing
class Dog { bark() { return "Woof!"; } }
class Cat { meow() { return "Meow!"; } }
function makeSound(animal: Dog | Cat): string {
if (animal instanceof Dog) {
return animal.bark(); // narrowed to Dog
}
return animal.meow(); // narrowed to Cat
}
// "in" operator narrowing
interface Fish { swim(): void; }
interface Bird { fly(): void; }
function move(creature: Fish | Bird) {
if ("swim" in creature) {
creature.swim(); // narrowed to Fish
} else {
creature.fly(); // narrowed to Bird
}
}
Discriminated Unions
// A "kind" field narrows the type
type Circle = { kind: "circle"; radius: number };
type Square = { kind: "square"; side: number };
type Triangle = { kind: "triangle"; base: number; height: number };
type Shape = Circle | Square | Triangle;
function area(shape: Shape): number {
switch (shape.kind) {
case "circle": return Math.PI * shape.radius ** 2;
case "square": return shape.side ** 2;
case "triangle": return 0.5 * shape.base * shape.height;
// TypeScript warns if you forget a case!
}
}
🏋️ Practice Task
Build a notification system. Types: EmailNotif (to, subject, body), SMSNotif (phone, text), PushNotif (deviceId, title, message). Union: Notification. Write send(n: Notification) that uses type narrowing to handle each type differently. Add a type predicate isUrgent(n: Notification): n is PushNotif.
💡 Hint: type predicate: function isUrgent(n: Notification): n is PushNotif { return “deviceId” in n; }