Type Narrowing & Type Guards
11 min read
Type Narrowing & Type Guards
When a variable has a union type, TypeScript narrows it to a more specific type inside conditional branches. This is called type narrowing, and it happens automatically based on the checks you write.
typeof narrowing
ts
function format(value: string | number): string {
if (typeof value === "string") {
return value.toUpperCase(); // string here
}
return value.toFixed(2); // number here
}in narrowing
ts
type Cat = { meow: () => void };
type Dog = { bark: () => void };
function speak(animal: Cat | Dog) {
if ("meow" in animal) {
animal.meow(); // Cat
} else {
animal.bark(); // Dog
}
}instanceof narrowing
ts
function handleError(error: unknown) {
if (error instanceof Error) {
console.error(error.message); // Error
} else {
console.error(String(error));
}
}Custom type guard functions
ts
interface Fish { swim: () => void; }
interface Bird { fly: () => void; }
// Return type 'pet is Fish' is the type predicate
function isFish(pet: Fish | Bird): pet is Fish {
return (pet as Fish).swim !== undefined;
}
function move(pet: Fish | Bird) {
if (isFish(pet)) {
pet.swim();
} else {
pet.fly();
}
}Narrowing also works with truthiness checks, equality (=== null), and the satisfies operator introduced in TypeScript 4.9. Always prefer structural narrowing over casting with as.
Ready to test yourself?
Practice TypeScript— quiz & coding exercises