TypeScript Type Guards & Narrowing
Type guards narrow union types to specific types, enabling type-safe operations on them.
typeof Guard
typeof narrows primitive types:
function processValue(value: string | number): void {
if (typeof value === "string") {
console.log(value.toUpperCase());
} else {
console.log(value.toFixed(2));
}
}
instanceof Guard
instanceof narrows based on class constructors:
class Dog { bark(): void {} }
class Cat { meow(): void {} }
function handleAnimal(animal: Dog | Cat): void {
if (animal instanceof Dog) {
animal.bark();
} else {
animal.meow();
}
}
Discriminated Unions
A literal property acts as a discriminator for automatic narrowing:
type Circle = { kind: "circle"; radius: number };
type Rectangle = { kind: "rectangle"; width: number; height: number };
type Shape = Circle | Rectangle;
function getArea(shape: Shape): number {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
case "rectangle":
return shape.width * shape.height;
}
}
Tip: Discriminated unions are the cleanest narrowing pattern.
Custom Type Guards
Use value is Type return type to create reusable guards:
function isString(value: unknown): value is string {
return typeof value === "string";
}
let value: unknown = "hello";
if (isString(value)) {
console.log(value.toUpperCase());
}
Truthiness and in Guards
Truthiness narrows null/undefined. in checks for property existence:
function process(value: string | null): void {
if (value) {
console.log(value.toUpperCase());
}
}
function move(animal: { swim(): void } | { fly(): void }): void {
if ("swim" in animal) {
animal.swim();
} else {
animal.fly();
}
}
Best Practices
- Use discriminated unions for the cleanest type narrowing
- Prefer
switchstatements to handle all cases - Use custom type guards for complex type checking
- Keep type guards simple — avoid excessive nesting
- Use truthiness checks for null/undefined narrowing
Common Mistakes
- Not using discriminated unions when possible
- Using
anyinstead of type guards - Forgetting a
defaultcase in switch statements - Confusing type guards with type assertions — guards narrow, assertions override
- Not using
value is Typein custom guard return types