How Type Narrowing Works in Typescript

Updated at August 2024

Type narrowing is the process in which typescript can clearly determine that an entity has a certain type.

A very basic example would be:

0
1
2
3
4
5
6
7
8
type MyType = string | number

function myFunction(prop: MyType) {
    if(typeof prop === "number") {
        // handle it like a number
    } else {
        // handle it like a type
    }
}

However, a more sophisticated example for type narrowing in typescript might look like this:

Assume that an API returns a status field that determines which other fields will be present in the response. In the first image, you can see a response where status: "VALID" includes fieldA, and another response where status: "INVALID" includes fieldB. Both might include a sharedFieldC, but it is optional.

0
1
2
3
4
5
6
7
8
const responseA: Response = {
    status: "VALID",
    fieldA: false
}

const responseB: Response = {
    status: "INVALID",
    fieldB: false
}

To clearly define this behavior in TypeScript, you can create an abstract type for shared fields and then define explicit variants of your response for each individual status.

In the second image, TypeScript uses type narrowing in the if/switch statement to infer which fields are available based on the status field.

0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
type AbstractApiResponse = {
    status: "VALID" | "INVALID"
    sharedFieldC?: boolean
}

type ValidResponse = AbstractApiResponse & {
    status: "VALID",
    fieldA: boolean
}

type InvalidResponse = AbstractApiResponse & {
    status: "INVALID",
    fieldB: boolean
}

type Response = InvalidResponse | ValidResponse

function responseHandler(response: Response) {
    if(response.status === "VALID") {
        response.fieldA
        response.fieldB // This will not compile
    } else {
        response.fieldA // This will not compile
        response.fieldB
    }
}

What Do You Gain From Type Narrowing In Typescript?

  • No need for assertion operators like ! or ?
  • Simple setup for a clearly defined API response structure
  • Improved readability by using TypeScript's type inference