Type guards are expressions that perform runtime checks to narrow types.
typeof Type Guards
Use typeof to check primitive types.
function process(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));
}
}
instanceof Type Guards
Use instanceof to check class instances.
class Dog {
bark(): void {
console.log("Woof!");
}
}
class Cat {
meow(): void {
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
}
}
in Operator Type Guards
Use in to check for properties.
interface Dog {
type: "dog";
bark(): void;
}
interface Cat {
type: "cat";
meow(): void;
}
function makeSound(pet: Dog | Cat) {
if ("bark" in pet) {
pet.bark(); // TypeScript knows it's a Dog
} else {
pet.meow(); // TypeScript knows it's a Cat
}
}
User-Defined Type Guards
Create custom type guard functions.
interface Fish {
swim(): void;
}
interface Bird {
fly(): void;
}
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 it's a Fish
} else {
pet.fly(); // TypeScript knows it's a Bird
}
}
Type Guard with Discriminated Union
type Shape =
| { kind: "circle"; radius: number }
| { kind: "rectangle"; width: number; height: number };
function isCircle(shape: Shape): shape is { kind: "circle"; radius: number } {
return shape.kind === "circle";
}
function area(shape: Shape): number {
if (isCircle(shape)) {
return Math.PI * shape.radius ** 2;
} else {
return shape.width * shape.height;
}
}
Assertion Functions
Functions that assert a condition (TypeScript 3.7+).
function assertIsNumber(value: unknown): asserts value is number {
if (typeof value !== "number") {
throw new Error("Expected number");
}
}
function process(value: unknown) {
assertIsNumber(value);
// TypeScript knows value is number here
console.log(value.toFixed(2));
}
Exercises
Exercise 1: typeof Guards
// Create a function that:
// - Takes string | number | boolean
// - Uses typeof to handle each case
// - Returns a formatted string for each type
Exercise 2: instanceof Guards
// Create classes Vehicle, Car, and Bike
// Create a function that uses instanceof to:
// - Call drive() for Vehicle
// - Call specific methods for Car and Bike
Exercise 3: User-Defined Guards
// Create a type guard 'isString' that:
// - Takes unknown
// - Returns value is string
// - Checks if value is actually a string
Exercise 4: Discriminated Union Guards
// Create a discriminated union for API responses
// Create type guards for success and error cases
// Use them to safely handle responses
Exercise 5: Assertion Functions
// Create an assertion function 'assertIsArray' that:
// - Takes unknown
// - Asserts it's an array
// - Throws if not
Best Practices
- Use type guards instead of assertions - More type-safe
- Create reusable type guards - For common checks
- Use discriminated unions - Makes narrowing easier
- Document type guards - Explain what they check
- Test type guards - Ensure they work correctly
Common Mistakes
- Not using type guards
// ❌ Using type assertion
function process(value: string | number) {
const str = value as string; // Unsafe!
str.toUpperCase();
}
// ✅ Using type guard
function process(value: string | number) {
if (typeof value === "string") {
value.toUpperCase(); // Safe
}
}
- Wrong type guard syntax
// ❌ Wrong return type
function isString(value: unknown): boolean {
return typeof value === "string";
}
// ✅ Correct - type predicate
function isString(value: unknown): value is string {
return typeof value === "string";
}
- Not handling all cases
// ❌ Missing else case
function process(value: string | number) {
if (typeof value === "string") {
value.toUpperCase();
}
// value might still be number here
}
// ✅ Handle all cases
function process(value: string | number) {
if (typeof value === "string") {
value.toUpperCase();
} else {
value.toFixed(2);
}
}
Next Steps
Learn about Type Narrowing.