Editor’s note: This article was last updated on 15 January 2024 to include the use of the TypeScript is
operator. It also now covers the creation of custom type guards and information about how to inspect objects with type guards.
A type guard is a TypeScript technique used to get information about the type of a variable, usually within a conditional block. Type guards are regular functions that return a Boolean, taking a type and telling TypeScript if it can be narrowed down to something more specific. Type guards have the unique property of assuring that the value tested is of a set type depending on the returned Boolean.
TypeScript uses built-in JavaScript operators like typeof
, instanceof
, and in
to determine if an object contains a property. Type guards enable you to instruct the TypeScript compiler to infer a specific type for a variable in a particular context. This process validates that the type of an argument aligns with the specified type, enhancing type accuracy and code reliability.
Type guards are typically used for narrowing a type and are quite similar to feature detection, allowing you to detect the correct methods, prototypes, and properties of a value. Therefore, you can easily figure out how to handle that value.
instanceof
type guardinstanceof
is a built-in type guard that can be used to check if a value is an instance of a given constructor function or class. With this type guard, we can test if an object or value is derived from a class, which is useful for determining the type of an instance type.
The basic syntax for the instanceof
type guard is below:
objectVariable instanceof ClassName;
In the example below, we see an example of the instanceof
type guard:
interface Accessory { brand: string; } class Necklace implements Accessory{ kind: string; brand: string; constructor(brand: string, kind: string) { this.brand = brand; this.kind = kind; } } class bracelet implements Accessory{ brand: string; year: number; constructor(brand: string, year: number) { this.brand = brand; this.year = year; } } const getRandomAccessory = () =>{ return Math.random() < 0.5 ? new bracelet('cartier', 2021) : new Necklace('choker', 'TASAKI'); } let Accessory = getRandomAccessory(); if (Accessory instanceof bracelet) { console.log(Accessory.year); } if (Accessory instanceof Necklace) { console.log(Accessory.brand); }
The getRandomAccessory
function above returns either a Necklace
or bracelet
object, as they both implement the Accessory
interface. The constructor signatures for both Necklace
and bracelet
are different, and the instanceof
type guard compares both constructor signatures to effectively determine the type.
typeof
type guardThe typeof
type guard is used to determine the type of a variable. The typeof
type guard is said to be very limited and shallow; it can only determine the following types recognized by JavaScript:
boolean
string
bigint
symbol
undefined
function
number
For anything outside of this list, the typeof
type guard simply returns object
.
The typeof
type guard can be written in the following two ways:
typeof v !== "typename" #or typeof v === "typename"
typename
can be a string
, number
, symbol
, or boolean
.
In the example below, StudentId
has a string | number
type union parameter entry. We see that if the variable is a string
, Student
is printed, and if it is a number
, Id
is printed. The typeof
type guard helps us to extract the type from x
:
function StudentId(x: string | number) { if (typeof x == 'string') { console.log('Student'); } if (typeof x === 'number') { console.log('Id'); } } StudentId(`446`); //prints Student StudentId(446); //prints Id
in
type guardThe in
type guard checks if an object has a particular property, using that to differentiate between different types. It usually returns a Boolean, which indicates if the property exists in that object. It is used for its narrowing features, as well as to check for browser support.
The basic syntax for the in
type guard is below:
propertyName in objectName
In the example below, the in
type guard checks if the property house
exists. In cases where it does exist, the Boolean true
is returned, and where it does not exist, false
is returned:
"house" in { name: "test", house: { parts: "door" } }; // => true "house" in { name: "test", house: { parts: "windows" } }; // => true "house" in { name: "test", house: { parts: "roof" } }; // => true "house" in { name: "test" }; // => false "house" in { name: "test", house: undefined }; // => true
Another similar example of how the in
type guard works is shown below:
interface Pupil { ID: string; } interface Adult { SSN: number; } interface Person { name: string; age: number; } let person: Pupil | Adult | Person = { name: 'Britney', age: 6 }; const getIdentifier = (person: Pupil | Adult | Person) => { if ('name' in person) { return person.name; } else if ('ID' in person) { return person.ID } return person.SSN; }
is
operatorThe is
operator checks if a value or variable is of a specific type. It is a type guard that can be used to narrow the type of a variable or expression. It is often used with a user-defined type guard function to narrow down the type of a variable within a specific code block.
This operator allows you to check whether a value is a certain type at runtime by type testing. This is particularly useful when you want to ensure that a variable has a specific type before performing certain operations on it.
The basic syntax for the is
operator is as follows:
variablename is typename
Here is a simple example of the is
operator in action:
interface Cat { meow(): void; } interface Dog { bark(): void; } function isCat(pet: Dog | Cat): pet is Cat { return (pet as Cat).meow !== undefined; } let pet: Dog | Cat; // Using the 'is' keyword if (isCat(pet)) { pet.meow(); } else { pet.bark(); }
The code above checks the type of the variable pet
and performs different actions based on its type. If pet
is of type Cat
, it calls the meow()
method. If pet
is of type Dog
, it calls the bark()
method.
The isCat()
function is a type guard that checks if pet
is of type Cat
by checking if the meow()
method exists on pet
. If it does, the function returns true
, indicating that pet
is of type Cat
. Otherwise, it returns false
. The is
keyword is used to perform the type check in the if
statement.
Equality narrowing checks for the value of an expression. For two variables to be equal, they must both be of the same type. If the type of a variable is unknown, but it is equal to another variable with a precise type, then TypeScript will narrow the type of the first variable with the information the well-known variable provides:
function getValues(a: number | string, b: string) { if(a === b) { // this is where the narrowing takes place. narrowed to string console.log(typeof a) // string } else { // if there is no narrowing, type remains unknown console.log(typeof a) // number or string } }
If variable a
is equal to variable b
, then both have to have the same type. In this case, TypeScript narrows it down to string. Without narrowing, the type of a
is still unclear because it could either be a number or a string.
Creating a custom type guard is typically the most powerful option for using type guards. When you create a custom type guard by writing it yourself, there are no limits to what you can check. However, if the custom type guard is written incorrectly, it can cause many errors. Therefore, precision is key.
An example of a custom type guard is shown below:
interface Necklace{ kind: string; brand: string; } interface bracelet{ brand: string; year: number; } type Accessory = Necklace | bracelet; const isNecklace = (b: Accessory): b is Necklace => { return (b as Necklace).kind !== undefined } const Necklace: Accessory = {kind: "Choker", brand: "TASAKI"}; const bracelet: Accessory = {brand: "Cartier", year: 2021}; console.log(isNecklace(bracelet)) //Logs false console.log(isNecklace(Necklace)) //Logs true
In the code above, the type predicate b is Necklace
will make TypeScript reduce the type to Necklace
instead of returning just a Boolean value.
TypeScript custom type guards are functions that enable you to check the type of a value or expression at runtime. They are useful for verifying more complex types or conditions that can’t be easily checked using built-in type guards like some we have already explored.
Custom type guards can be used in conditional expressions and other parts of your code where you need to check the type of a value or expression.
Custom type guards allow you to check for more complex types and conditions and generally improve the readability of your code.
Here is an example of a custom type guard:
function isBaby(obj: any): obj is Baby { return typeof obj === "object" && obj !== null && "sound" in obj; } interface Baby { sound: string; } function makeSound(baby: any) { if (isBaby(baby)) { console.log("Making a sound:", baby.sound); } else { console.log("Not a valid baby."); } }
Above, the function makeSound
takes the argument baby
of type any
. It checks if the baby
object satisfies the isBaby
type guard, which checks if the object has a property called sound
. If the baby
object satisfies the isBaby
type guard, it logs the sound of the baby to the console. Otherwise, it logs “Not a valid baby.
” to the console.
isBaby
is our custom type guard function. It checks if an object has a sound
property, which is characteristic of a baby
.
Type guards can be used to inspect or check the type of an object. Type guards can be used to check the specific properties an object has, thus allowing us to perform operations specific to those properties.
We can also use type guards to check if an object is of a particular type, like a number, string, or a specific class:
function isCar(vehicle: any): vehicle is Car { return ( typeof vehicle === "object" && vehicle !== null && "brand" in vehicle && "model" in vehicle && "year" in vehicle ); } interface Car { brand: string; model: string; year: number; } function inspectVehicle(vehicle: any) { if (isCar(vehicle)) { // Inside this block, TypeScript knows that 'vehicle' is of type 'Car' console.log("Brand:", vehicle.brand); console.log("Model:", vehicle.model); console.log("Year:", vehicle.year); } else { console.log("Not a valid car object."); } }
The code above checks if a given object is of type Car
. It includes an interface Car
that defines the properties brand
, model
, and year
. It also includes a function inspectVehicle
that takes an object as a parameter and checks if it is a Car
using the isCar
type guard function. If it is a Car
, it logs the brand, model, and year of the vehicle. Otherwise, it logs that it is not a valid car object.
We use the inspectVehicle
function to check if the vehicle
parameter is indeed a Car
. If the vehicle
is a Car
, it logs the brand, model, and year of the vehicle. If the vehicle
is not a Car
, it logs that it is not a valid car object.
We can test with a car object as seen in the code block below:
const myCar = { brand: "Toyota", model: "Camry", year: 2020 }; inspectVehicle(myCar);
Or test with a non-car object as seen here:
const myBike = { type: "Mountain Bike", wheels: 2 }; inspectVehicle(myBike);
TypeScript type guards help assure the value of a type, improving the overall code flow. In this article, we reviewed several of the most helpful type guards in TypeScript, exploring a few examples to see them in action.
Oftentimes, your use case can be solved using either the instanceof
type guard, the typeof
type guard, or the in
type guard. However, you can use a custom type guard when it is necessary.
I hope you enjoyed this article! Be sure to leave a comment if you have any questions.
LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.
In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page and mobile apps.
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowCompare Prisma and Drizzle ORMs to learn their differences, strengths, and weaknesses for data access and migrations.
It’s easy for devs to default to JavaScript to fix every problem. Let’s use the RoLP to find simpler alternatives with HTML and CSS.
Learn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.
Bypass anti-bot measures in Node.js with curl-impersonate. Learn how it mimics browsers to overcome bot detection for web scraping.
6 Replies to "How to use type guards in TypeScript"
It is recommended to name variables camelCase and class/interface/types PascalCase
Nice post.
The section on the typeof operator is somewhat incorrect. I can be string, number, boolean, or symbol ALONG WITH function, object, and bigint.
The section “The typeof type guard” may mislead the readers unless the author corrects those type with the lowercased types.
That’s a fair point, thanks for the suggestion
Crazy language… The only way to really check for union typed classInstance is to have some unique filed name in it and do “if (uniqueFieldName in classInstance)”… *blarghhhhh