Skip to main content

TypeScript Type Guards

A type guard is a check that narrows the type of a variable inside a block of code. After the check, TypeScript knows the more specific type and lets you use it safely.


Understanding Type Guards in TypeScript

Type guards are explicit checks that decide the exact type of a value at runtime. TypeScript reads these checks and narrows the type in the code that follows.

Why use type guards?

  • Type safety: operations only run on values of the right type
  • Code clarity: the type check is explicit and documents itself
  • Better tooling: the editor gives accurate completion for the narrowed type
  • Error prevention: type mistakes are caught at compile time
  • Runtime safety: the check also protects the code while it runs

Type guard patterns covered on this page:

  • typeof type guards
  • instanceof type guards
  • User-defined type guards with type predicates
  • Discriminated unions with literal types
  • in operator type guards
  • Type assertion functions

typeof Type Guards

The typeof operator is a built-in type guard. It checks the type of a primitive value at runtime.

It is most useful for narrowing unions of primitives such as string, number and boolean.

Basic Usage

Inside the if (typeof value === 'string') branch, TypeScript treats value as a string. In the else branch it is a number. Each branch can call the methods of its own type.

Prompt
Show a typeof type guard narrowing a string | number parameter. Write a formatValue(value: string | number): string function that trims and upper-cases the value in the string branch and calls toFixed(2) in the number branch, then call it with ' hello ' and 42.1234, storing the results in result1 and result2 with comments showing "HELLO" and "42.12". The example does not print anything.
Output: TypeScript file and captured output

instanceof Type Guards

The instanceof operator checks whether an object was created by a specific class or constructor function.

Use it to narrow unions of class instances or built-in objects.

Class-based Type Guarding

Checking animal instanceof Bird narrows animal to Bird in the first branch, so fly() is allowed. In the else branch it must be a Fish.

Prompt
Show an instanceof type guard narrowing a union of two classes. Declare a Bird class with a fly() method that logs Flying... and a Fish class with a swim() method that logs Swimming..., then a move(animal: Bird | Fish) function that calls fly() when animal instanceof Bird and swim() otherwise. The example only defines the function and prints nothing.
Output: TypeScript file and captured output

User-Defined Type Guards

For more complex checks you can write your own type guard function. Its return type is a type predicate of the form parameterName is Type.

Type Predicate Functions

The signature vehicle is Car tells TypeScript: when this function returns true, treat vehicle as a Car. In the else branch it is narrowed to Motorcycle.

Prompt
Show a user-defined type guard that returns a type predicate. Declare Car (make, model, year, plus an optional type?: undefined so it cannot be confused with a motorcycle) and Motorcycle (same fields plus type: "sport" | "cruiser") interfaces, an isCar(vehicle: Car | Motorcycle): vehicle is Car function that checks whether the type property is undefined, and a displayVehicleInfo function that logs make, model and year, then logs This is a car or This is a <type> motorcycle depending on the guard. Only definitions, no calls, so nothing is printed.
Output: TypeScript file and captured output

Discriminated Unions

A discriminated union (also called a tagged union) is a union of object types that share one common property, the discriminant. Its literal value tells the types apart.

This pattern works very well together with type guards.

Basic Discriminated Union

The shared kind property is the discriminant. A switch on shape.kind narrows shape to Circle or Square in each case.

Prompt
Show a discriminated union narrowed with a switch on the discriminant. Declare Circle (kind: "circle", radius) and Square (kind: "square", sideLength) interfaces, a Shape union, and a calculateArea(shape: Shape) function that switches on shape.kind and returns Math.PI * radius ** 2 for circles and sideLength ** 2 for squares. Only definitions, nothing is printed.
Output: TypeScript file and captured output

The in Operator

The in operator checks whether an object has a given property.

It is useful for narrowing unions whose members have different property names.

Property Existence Checking

Only Dog has a bark property, so "bark" in animal narrows animal to Dog. Otherwise it must be a Cat.

Prompt
Show the in operator used as a type guard. Declare a Dog interface with a bark(): void method and a Cat interface with meow(): void, then a makeSound(animal: Dog | Cat) function that calls bark() when "bark" in animal and meow() otherwise. Only definitions, nothing is printed.
Output: TypeScript file and captured output

Type Assertion Functions

A type assertion function is a special type guard. Instead of returning true or false, it throws an error when the check fails. After the call, TypeScript treats the value as the asserted type.

They are useful for validating data at runtime.

Assertion Functions

asserts value is string narrows value to string after the call. asserts condition tells TypeScript that the condition is true from that point on, so value becomes a number.

Prompt
Show type assertion functions that throw on invalid input. Write assertIsString(value: unknown): asserts value is string that throws Value is not a string for non-strings, and assert(condition: unknown, message: string): asserts condition that throws the message when the condition is falsy. Then write processInput(input: unknown) that calls assertIsString and logs input.toUpperCase(), and processNumber(value: unknown): number that asserts typeof value === 'number' and returns value * 2. Only definitions, nothing is printed.
Output: TypeScript file and captured output

Best Practices

When to use each type guard:

  • Use typeof for primitive types (string, number, boolean, and so on)
  • Use instanceof for class instances and built-in objects
  • Use user-defined type guards for complex validation logic
  • Use discriminated unions for related types that share a discriminant property
  • Use the in operator to check whether a property exists
  • Use type assertion functions for runtime validation that should throw

Performance considerations:

  • typeof and instanceof are very fast
  • Avoid heavy logic inside user-defined type guards when performance matters
  • Put expensive checks that run many times into a type predicate function