TypeScript – Type Guards Explained with Examples & Best Practices
Introduction – What Are Type Guards in TypeScript?
In TypeScript, type guards are expressions or functions that narrow down the type of a variable within a conditional block. They help the TypeScript compiler understand the actual runtime type of a value, improving type safety and enabling precise code paths.
In this guide, you’ll learn:
- What type guards are and why they’re useful
- Different types of type guards:
typeof,instanceof, custom guards, etc. - How to write your own type guard functions
- Real-world examples and best practices
What Is a Type Guard?
A type guard is a conditional check that tells TypeScript:
“Inside this block, this variable has this specific type.”
Example:
function printLength(value: string | number) {
if (typeof value === "string") {
console.log(value.length); // Safe: value is string here
} else {
console.log(value.toFixed(2)); // Safe: value is number here
}
}
Explanation:
- TypeScript narrows the union type (
string | number) to the correct type inside each branch.
1. typeof Type Guard
Used to check primitive types like string, number, boolean, undefined, and symbol.
Example:
function formatValue(val: string | number) {
if (typeof val === "string") {
return val.toUpperCase();
}
return val.toFixed(2);
}
Supported Types:
"string""number""boolean""undefined""symbol""bigint""function"
2. instanceof Type Guard
Used to check if a value is an instance of a class or constructor function.
Example:
class Dog {
bark() {
console.log("Woof!");
}
}
class Cat {
meow() {
console.log("Meow!");
}
}
function makeSound(pet: Dog | Cat) {
if (pet instanceof Dog) {
pet.bark();
} else {
pet.meow();
}
}
Explanation:
- TypeScript understands which class the object belongs to based on the
instanceofcheck.
3. Discriminated Unions (Tagged Unions)
A type guard using a common discriminant property (often a string literal).
Example:
type Circle = { kind: "circle"; radius: number };
type Square = { kind: "square"; side: number };
type Shape = Circle | Square;
function area(shape: Shape): number {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
case "square":
return shape.side ** 2;
}
}
Explanation:
kindacts as a tag that differentiates the object type.- Ideal for APIs and reducers with multiple shape types.
4. Custom Type Guard Functions
You can define your own type guard function using a special return type:value is Type
Example:
type Admin = { role: "admin"; accessLevel: number };
type Guest = { role: "guest"; expiresIn: number };
type User = Admin | Guest;
function isAdmin(user: User): user is Admin {
return user.role === "admin";
}
function checkAccess(user: User) {
if (isAdmin(user)) {
console.log(`Admin access level: ${user.accessLevel}`);
} else {
console.log(`Guest expires in: ${user.expiresIn}`);
}
}
Explanation:
isAdminis a custom guard that narrowsusertoAdmininside theifblock.- Useful for complex checks that can’t be handled with
typeoforinstanceof.
5. Truthy Guards (Caution Required)
TypeScript also narrows types based on truthiness checks, but this can be error-prone.
Example:
function showLength(text?: string) {
if (text) {
console.log(text.length); // text is narrowed to string
}
}
Be careful: 0, "", false, and null are also falsy.
6. in Operator for Property Checking
You can use "property" in obj to confirm if an object has a property.
Example:
type Car = { model: string; engine: string };
type Bicycle = { model: string; gears: number };
type Vehicle = Car | Bicycle;
function describe(vehicle: Vehicle) {
if ("engine" in vehicle) {
console.log(`Car engine: ${vehicle.engine}`);
} else {
console.log(`Bicycle gears: ${vehicle.gears}`);
}
}
Common Mistakes & How to Avoid Them
| Mistake | Solution |
|---|---|
Using typeof on non-primitive types | Use instanceof or custom type guards |
Forgetting to use user is Type | Always use return type annotation for custom guards |
| Trusting truthy checks blindly | Use explicit type checks when needed |
Best Practices
- Use
typeoffor primitive checks - Use
instanceoffor class-based logic - Prefer custom type guards for advanced logic
- Use discriminated unions for scalable type-safe APIs
- Don’t rely only on truthy/falsy checks for narrowing
Summary – Recap & Next Steps
Type guards are essential for writing robust, type-safe logic in TypeScript. They allow you to safely work with union types, custom objects, and conditional flows.
Key Takeaways:
- Type guards narrow a variable’s type within a scope
- Use
typeof,instanceof,in, or discriminants for safe checks - Build custom type guards with
value is Typereturn - Avoid relying solely on truthy/falsy logic
Real-world relevance: Type guards power form validation, API parsing, DOM checks, and business logic in modern TypeScript applications.
FAQs – Type Guards in TypeScript
Can I use type guards on union types?
Yes, that’s the main use case—to narrow a union type to a specific subtype.
Is typeof valid for custom classes?
No. Use instanceof for class checks, not typeof.
Can I use type guards in arrow functions?
Yes. Just make sure to specify the return type like:
const isString = (x: any): x is string => typeof x === "string";
Do type guards exist in JavaScript?
No. They are a TypeScript-specific feature built on top of JavaScript logic.
Share Now :
