function formatId(id: string | number): string {
if (typeof id === "string") {
return id.toUpperCase(); // TypeScript knows id is a string here
}
return id.toFixed(2); // and knows it's a number here
}
This is type narrowing — after the typeof check, TypeScript restricts what it believes id could be inside each branch, and only allows the methods valid for that narrowed type. Get the check wrong (or skip it) and the compiler stops you before a .toUpperCase() call ever hits a number at runtime.
Common narrowing techniques, beyond typeof:
// instanceof — narrowing by class
function handle(error: Error | string) {
if (error instanceof Error) {
console.log(error.message); // narrowed to Error
} else {
console.log(error.toUpperCase()); // narrowed to string
}
}
// "in" — narrowing by property presence
interface Circle { kind: "circle"; radius: number; }
interface Square { kind: "square"; side: number; }
function area(shape: Circle | Square) {
if ("radius" in shape) return Math.PI * shape.radius ** 2; // narrowed to Circle
return shape.side ** 2; // narrowed to Square
}
// Truthiness narrowing
function printLength(s: string | null) {
if (s) console.log(s.length); // narrowed to string — null is falsy, filtered out
}