🛡️ 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
instanceof
check.
🧩 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:
kind
acts 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:
isAdmin
is a custom guard that narrowsuser
toAdmin
inside theif
block.- Useful for complex checks that can’t be handled with
typeof
orinstanceof
.
🧼 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
typeof
for primitive checks - ✅ Use
instanceof
for 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 Type
return - 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 :