TypeScript Type Guards
Runtime checks for type safety
🛡️ What are Type Guards?
Type Guards are runtime checks that help TypeScript understand and narrow down the specific type of a variable within a conditional block, ensuring type safety during code execution.
// Simple type guard example
function isString(value: unknown): value is string {
return typeof value === 'string';
}
if (isString(input)) {
console.log(input.toUpperCase()); // TypeScript knows it's a string
}
Output:
✅ Type safely checked and narrowed
Key Type Guard Concepts
typeof Guard
Check primitive types at runtime
if (typeof x === 'string') {
x.toUpperCase();
}
instanceof Guard
Check class instances
if (obj instanceof Date) {
obj.getTime();
}
Custom Guards
Create your own type predicates
function isFish(pet: Fish | Bird):
pet is Fish {
return 'swim' in pet;
}
in Operator
Check property existence
if ('name' in obj) {
console.log(obj.name);
}
🔹 typeof Type Guard
Use typeof to check primitive types:
function processValue(value: string | number) {
if (typeof value === 'string') {
// TypeScript knows value is string here
console.log(value.toUpperCase());
} else {
// TypeScript knows value is number here
console.log(value.toFixed(2));
}
}
processValue('hello'); // Output: HELLO
processValue(42.567); // Output: 42.57
// Multiple type checks
function printValue(val: string | number | boolean) {
if (typeof val === 'string') {
console.log(`String: ${val}`);
} else if (typeof val === 'number') {
console.log(`Number: ${val}`);
} else {
console.log(`Boolean: ${val}`);
}
}
Output:
HELLO
42.57
🔹 instanceof Type Guard
Check if an object is an instance of a class:
class Dog {
bark() {
console.log('Woof!');
}
}
class Cat {
meow() {
console.log('Meow!');
}
}
function makeSound(animal: Dog | Cat) {
if (animal instanceof Dog) {
animal.bark(); // TypeScript knows it's a Dog
} else {
animal.meow(); // TypeScript knows it's a Cat
}
}
const myDog = new Dog();
const myCat = new Cat();
makeSound(myDog); // Output: Woof!
makeSound(myCat); // Output: Meow!
Output:
Woof!
Meow!
🔹 Custom Type Guards
Create custom type predicates with "is" keyword:
interface Fish {
swim: () => void;
}
interface Bird {
fly: () => void;
}
// Custom type guard function
function isFish(pet: Fish | Bird): pet is Fish {
return (pet as Fish).swim !== undefined;
}
function movePet(pet: Fish | Bird) {
if (isFish(pet)) {
pet.swim(); // TypeScript knows it's Fish
} else {
pet.fly(); // TypeScript knows it's Bird
}
}
// Another example
interface User {
name: string;
email: string;
}
function isUser(obj: any): obj is User {
return obj && typeof obj.name === 'string' && typeof obj.email === 'string';
}
🔹 in Operator Type Guard
Check if a property exists in an object:
type Admin = {
name: string;
privileges: string[];
};
type Employee = {
name: string;
startDate: Date;
};
type UnknownEmployee = Admin | Employee;
function printEmployeeInfo(emp: UnknownEmployee) {
console.log(`Name: ${emp.name}`);
if ('privileges' in emp) {
console.log(`Privileges: ${emp.privileges.join(', ')}`);
}
if ('startDate' in emp) {
console.log(`Start Date: ${emp.startDate.toDateString()}`);
}
}
const admin: Admin = {
name: 'John',
privileges: ['create-server', 'delete-user']
};
printEmployeeInfo(admin);
Output:
Name: John
Privileges: create-server, delete-user
🔹 Discriminated Unions
Use a common property to distinguish types:
interface Circle {
kind: 'circle';
radius: number;
}
interface Square {
kind: 'square';
sideLength: number;
}
interface Rectangle {
kind: 'rectangle';
width: number;
height: number;
}
type Shape = Circle | Square | Rectangle;
function getArea(shape: Shape): number {
switch (shape.kind) {
case 'circle':
return Math.PI * shape.radius ** 2;
case 'square':
return shape.sideLength ** 2;
case 'rectangle':
return shape.width * shape.height;
}
}
const myCircle: Circle = { kind: 'circle', radius: 5 };
console.log(getArea(myCircle)); // Output: 78.54
Output:
78.54