HomeToolsAbout

Type Guard/Narrowing

Type Narrowing and Control Flow Analysis

What is Type Narrowing

Type Guards help you narrow down types to a more specific type than initially declared

  • Type narrowing is a way of telling a TS compiler a type variable should be understood/known as the specified type

Why use it

Narrowing asserts/ensures that the function performs the correct action based on specified type and makes your code less error prone

function padLeft(padding: number | string, input: string): string { return " ".repeat(padding) + input; } /** Argument of type 'string | number' is not assignable to parameter of type 'number'. Type 'string' is not assignable to type 'number'. */

Because the .repeat() method exclusively takes number as a parameter type, initially typed parameter of string | number doesn't match the type expectation

By narrowing the type down, you can create different behaviors within the function based on the type

function padLeft(padding: number | string, input: string): string { // type of parameter `padding` here is type `number` via narrowing if (typeof padding === "number") { return " ".repeat(padding) + input; } // type of parameter `padding` here is type `string` return padding + input; }

Control Flow

This analysis of code based on reachability is called control flow analysis, and TypeScript uses this flow analysis to narrow types as it encounters type guards and assignments.

  • When a variable is analyzed, control flow can split off and re-merge over and over again, and that variable can be observed to have a different type at each point. (See Type Predicate section)

Important to note that Typescript is about compile time errors. Since Typescript is compiled to Javascript, there is no runtime type guarantee

What is Type Guard

Type guards are a function that takes a type and returns a boolean, telling TS that type can be arrowed down to something more specific

  • A type guard is some expression that performs a runtime check that guarantees the type in some scope

typeof

if (typeof a === 'string' && typeof b === 'string') { return a.concat(b); }

Returns a string based on the type of the value passed

"string", "number", "bigint", "boolean", "symbol", "undefined", "object", "function"

Truthiness narrowing

Using if(), !!, &&, ||, or Boolean(), your function can guard against falsy values

What are falsy Values: 0, NaN, "", 0n, null, undefined

function getUsersOnlineMessage(numUsersOnline: number) { if (numUsersOnline) { return `There are ${numUsersOnline} online now!`; } return "Nobody's here. :("; }

Equality narrowing

Using switch, ===, !==, ==, or != to narrow types

function example(x: string | number, y: string | boolean) { // only time below statement is true is when `x` and `y` are both `string` type if (x === y) { // We can now call 'string' type method on 'x' or 'y'. x.toUpperCase(); y.toLowerCase(); } }

in

Determines if an object or its prototype chain has a property with a given name

type Fish = { swim: () => void }; type Bird = { fly: () => void }; function move(animal: Fish | Bird) { if ("swim" in animal) { return animal.swim(); } return animal.fly(); }

instanceof

Used to check if a value is an instance of a given constructor function or class

  • We can test if an object or value is derived from a class
function logValue(x: Date | string) { if (x instanceof Date) { console.log(x.toUTCString()); } else { console.log(x.toUpperCase()); } }

Predicate

See Type Predicate Section

AboutContact