TypeScript Type Guards
A type guard is a condition that narrows a value's type to something more specific. When a variable holds a union type (like string | number), TypeScript cannot safely call type-specific methods without first checking which type the value actually is. Type guards perform that check, and TypeScript uses the result to narrow the type inside each branch of the condition.
Why Type Guards Are Needed
Variable: number | string
|
v
TypeScript sees a union — no type-specific methods available yet
|
+-------+-------+
| |
Check type Use without check
| |
Narrowed ✓ Error ✗ — TypeScript blocks it
Example:
input.toUpperCase(); // Error — method only exists on string, not number
if (typeof input === "string") {
input.toUpperCase(); // Valid — TypeScript knows it's string here
}
typeof Type Guard
The typeof operator checks the primitive type of a value. TypeScript understands typeof checks and narrows the type inside the condition block.
function describeValue(value: number | string | boolean): string {
if (typeof value === "string") {
return "String with length: " + value.length;
}
if (typeof value === "number") {
return "Number squared: " + value * value;
}
return "Boolean: " + value;
}
console.log(describeValue("TypeScript")); // String with length: 10
console.log(describeValue(7)); // Number squared: 49
console.log(describeValue(true)); // Boolean: true
instanceof Type Guard
The instanceof operator checks if an object was created from a specific class. TypeScript narrows to that class type inside the condition.
class Car {
drive(): void { console.log("Car is driving"); }
}
class Boat {
sail(): void { console.log("Boat is sailing"); }
}
function move(vehicle: Car | Boat): void {
if (vehicle instanceof Car) {
vehicle.drive(); // Narrowed to Car
} else {
vehicle.sail(); // Narrowed to Boat
}
}
move(new Car()); // Car is driving
move(new Boat()); // Boat is sailing
in Operator Type Guard
The in operator checks if a property exists in an object. TypeScript uses this to distinguish between object types that share some properties but have different unique ones.
type Bird = {
name: string;
fly(): void;
wingSpan: number;
};
type Fish = {
name: string;
swim(): void;
finCount: number;
};
function move(animal: Bird | Fish): void {
if ("fly" in animal) {
// Narrowed to Bird — has the 'fly' property
console.log(animal.name + " flies with span " + animal.wingSpan + "m");
} else {
// Narrowed to Fish — has the 'swim' property
console.log(animal.name + " swims with " + animal.finCount + " fins");
}
}
move({ name: "Eagle", fly() {}, wingSpan: 2.1 });
// Eagle flies with span 2.1m
move({ name: "Tuna", swim() {}, finCount: 7 });
// Tuna swims with 7 fins
Equality Narrowing
Comparing a value to a specific literal (using === or !==) narrows the type to that literal value inside the condition.
type Status = "active" | "inactive" | "pending";
function getStatusMessage(status: Status): string {
if (status === "active") {
return "Account is currently active.";
}
if (status === "inactive") {
return "Account has been deactivated.";
}
return "Account is awaiting approval."; // status must be "pending" here
}
console.log(getStatusMessage("active")); // Account is currently active.
console.log(getStatusMessage("pending")); // Account is awaiting approval.
User-Defined Type Guards
A user-defined type guard is a function that returns a type predicate — a special return type of the form parameter is Type. When this function returns true, TypeScript narrows the parameter to the specified type.
type Cat = { meow(): void; lives: number; };
type Dog = { bark(): void; breed: string; };
// Type predicate function
function isCat(animal: Cat | Dog): animal is Cat {
return (animal as Cat).meow !== undefined;
}
function interact(animal: Cat | Dog): void {
if (isCat(animal)) {
console.log("Cat with " + animal.lives + " lives");
animal.meow();
} else {
console.log("Dog breed: " + animal.breed);
animal.bark();
}
}
let myCat: Cat = { lives: 9, meow() { console.log("Meow!"); } };
let myDog: Dog = { breed: "Labrador", bark() { console.log("Woof!"); } };
interact(myCat); // Cat with 9 lives → Meow!
interact(myDog); // Dog breed: Labrador → Woof!
Assertion Functions
An assertion function throws an error if a condition is false and tells TypeScript to narrow the type after the call. Use the asserts keyword in the return type.
function assertIsString(value: unknown): asserts value is string {
if (typeof value !== "string") {
throw new Error("Expected string, got: " + typeof value);
}
}
let data: unknown = "Hello";
assertIsString(data);
// After this line, TypeScript knows 'data' is a string
console.log(data.toUpperCase()); // HELLO
Type Guard Methods Summary
| Type Guard | Syntax | Best For |
|---|---|---|
typeof | typeof x === "string" | Primitives (string, number, boolean) |
instanceof | x instanceof ClassName | Class instances |
in | "prop" in obj | Object types with unique properties |
| Equality | x === "value" | Literal union types |
| Type predicate | x is Type return | Custom complex checks |
| Assertion function | asserts x is Type | Throwing on invalid types |
Practical Example
// Payment processing with type guards
type CreditCardPayment = {
method: "credit";
cardNumber: string;
expiryDate: string;
cvv: string;
};
type UPIPayment = {
method: "upi";
upiId: string;
};
type NetBankingPayment = {
method: "netbanking";
bankName: string;
accountNumber: string;
};
type Payment = CreditCardPayment | UPIPayment | NetBankingPayment;
function processPayment(payment: Payment, amount: number): void {
console.log("Processing ₹" + amount + " via " + payment.method.toUpperCase());
switch (payment.method) {
case "credit":
console.log("Card ending: " + payment.cardNumber.slice(-4));
break;
case "upi":
console.log("UPI ID: " + payment.upiId);
break;
case "netbanking":
console.log("Bank: " + payment.bankName);
break;
}
}
processPayment({ method: "upi", upiId: "rahul@paytm" }, 1499);
// Processing ₹1499 via UPI
// UPI ID: rahul@paytm
processPayment({ method: "credit", cardNumber: "4111111111111234", expiryDate: "12/27", cvv: "123" }, 2999);
// Processing ₹2999 via CREDIT
// Card ending: 1234
