3️⃣ 🧪 TypeScript Core & Special Types
Estimated reading: 4 minutes 369 views

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 narrows user to Admin inside the if block.
  • Useful for complex checks that can’t be handled with typeof or instanceof.

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 typesUse instanceof or custom type guards
Forgetting to use user is TypeAlways use return type annotation for custom guards
Trusting truthy checks blindlyUse 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 :
Share

TypeScript — Type Guards

Or Copy Link

CONTENTS
Scroll to Top