TypeScript Type Guards and Narrowing
TypeScript's type system allows variables to have multiple possible types (Unions). Narrowing is the process of refining a broad type (like string | number) into a specific type (like string) within a specific block of code.
1. Built-in Type Guards
TypeScript automatically understands standard JavaScript checks.
typeof
Used for primitives (string, number, boolean, symbol).
function printId(id: number | string) {
if (typeof id === "string") {
// In this block, TypeScript knows 'id' is a string
console.log(id.toUpperCase());
} else {
// Here, 'id' must be a number
console.log(id.toFixed(2));
}
}
instanceof
Used for classes and objects constructed with new.
class Dog {
bark() { console.log("Woof!"); }
}
class Cat {
meow() { console.log("Meow!"); }
}
function interact(pet: Dog | Cat) {
if (pet instanceof Dog) {
pet.bark(); // OK
} else {
pet.meow(); // OK
}
}
in operator
Checks if a property exists on an object.
type Fish = { swim: () => void };
type Bird = { fly: () => void };
function move(animal: Fish | Bird) {
if ("swim" in animal) {
animal.swim();
} else {
animal.fly();
}
}
2. User-Defined Type Guards (Type Predicates)
Sometimes TypeScript can't figure out the type automatically. You can write a function that returns a type predicate.
The return type syntax is parameterName is Type.
interface Fish { swim: () => void; }
interface Bird { fly: () => void; }
// Custom Type Guard
function isFish(pet: Fish | Bird): pet is Fish {
return (pet as Fish).swim !== undefined;
}
function move(pet: Fish | Bird) {
if (isFish(pet)) {
pet.swim(); // TypeScript knows pet is Fish here
} else {
pet.fly(); // TypeScript knows pet is Bird here
}
}
3. Discriminated Unions
This is a powerful pattern where every type in a union has a common literal property (the "discriminant"), usually called kind or type.
interface Circle {
kind: "circle";
radius: number;
}
interface Square {
kind: "square";
sideLength: number;
}
type Shape = Circle | Square;
function getArea(shape: Shape) {
switch (shape.kind) {
case "circle":
// TypeScript knows this is a Circle
return Math.PI * shape.radius ** 2;
case "square":
// TypeScript knows this is a Square
return shape.sideLength ** 2;
}
}