Lesson 40 of 60 – Type Narrowing
67%

Type Narrowing in TypeScript

Type narrowing is the process of reducing a value from a broader type, such as a union type, to a more specific type. TypeScript uses checks in your code to understand which type a value has at a particular point.

Note: Type narrowing is especially useful when working with union types. It allows you to safely use methods and properties that belong to a specific type.

1. What is Type Narrowing?

Type narrowing means changing a broad type into a more specific type based on a condition.

function printValue(
    value: string | number
) {

    if (typeof value === "string") {
        console.log(
            value.toUpperCase()
        );
    }
}

Inside the if block, TypeScript knows that value is a string.

2. Why is Type Narrowing Needed?

A union type can contain different types. Some operations may be available only on one of those types.

function showValue(
    value: string | number
) {

    // value.toUpperCase();
    // Error because value
    // may be a number.

}

We narrow the type before using type-specific operations.

3. Narrowing with typeof

The typeof operator is one of the most common ways to narrow primitive types.

function showValue(
    value: string | number
) {

    if (typeof value === "string") {

        console.log(
            value.toUpperCase()
        );

    } else {

        console.log(
            value.toFixed(2)
        );

    }
}

4. Narrowing a String

When TypeScript knows that a value is a string, string methods become available.

function display(
    value: string | number
) {

    if (typeof value === "string") {

        console.log(
            value.toUpperCase()
        );

        console.log(
            value.length
        );

    }
}

5. Narrowing a Number

TypeScript can also narrow a union to a number.

function display(
    value: string | number
) {

    if (typeof value === "number") {

        console.log(
            value.toFixed(2)
        );

    }

}

6. Narrowing a Boolean

Boolean values can also be narrowed using typeof.

function show(
    value: string | boolean
) {

    if (typeof value === "boolean") {

        console.log(
            value
        );

    }

}

7. typeof Values

Common results of the JavaScript typeof operator include:

  • "string"
  • "number"
  • "boolean"
  • "undefined"
  • "object"
  • "function"
const value = "Hello";

if (typeof value === "string") {
    console.log("It is a string");
}

8. Narrowing with if...else

An if...else statement can divide a union into different possible types.

function showValue(
    value: string | number
) {

    if (typeof value === "string") {

        console.log(
            "String:",
            value
        );

    } else {

        console.log(
            "Number:",
            value
        );

    }
}

9. Narrowing with Multiple Conditions

Multiple type checks can be used for a union containing several types.

function showValue(
    value:
        string |
        number |
        boolean
) {

    if (typeof value === "string") {

        console.log(
            "String"
        );

    } else if (
        typeof value === "number"
    ) {

        console.log(
            "Number"
        );

    } else {

        console.log(
            "Boolean"
        );

    }
}

10. Narrowing with Array.isArray()

Array.isArray() can help narrow a value to an array.

function showData(
    value: string | string[]
) {

    if (Array.isArray(value)) {

        console.log(
            value.length
        );

    } else {

        console.log(
            value.toUpperCase()
        );

    }
}

11. Narrowing with in Operator

The in operator can help distinguish between object types based on their properties.

interface Student {
    name: string;
    course: string;
}

interface Teacher {
    name: string;
    subject: string;
}

function showUser(
    user: Student | Teacher
) {

    if ("course" in user) {

        console.log(
            user.course
        );

    } else {

        console.log(
            user.subject
        );

    }
}

12. Understanding the in Operator

The in operator checks whether a property exists in an object.

interface Admin {
    name: string;
    permissions: string[];
}

interface Student {
    name: string;
    course: string;
}

function showUser(
    user: Admin | Student
) {

    if ("permissions" in user) {

        console.log(
            user.permissions
        );

    }

}

13. Narrowing with instanceof

The instanceof operator can narrow values involving classes.

class Student {
    name: string;

    constructor(name: string) {
        this.name = name;
    }
}

class Teacher {
    subject: string;

    constructor(subject: string) {
        this.subject = subject;
    }
}

function show(
    person: Student | Teacher
) {

    if (person instanceof Student) {

        console.log(
            person.name
        );

    } else {

        console.log(
            person.subject
        );

    }
}

14. Narrowing with Equality

Equality checks can also narrow a union type.

function showValue(
    value: string | number
) {

    if (value === "Amit") {

        console.log(
            "The value is Amit"
        );

    } else {

        console.log(
            value
        );

    }
}

15. Narrowing with Literal Types

Literal properties can be used to narrow object unions.

interface Admin {
    role: "admin";
    name: string;
}

interface Student {
    role: "student";
    name: string;
}

type User =
    Admin | Student;

function showUser(
    user: User
) {

    if (user.role === "admin") {

        console.log(
            "Admin:",
            user.name
        );

    } else {

        console.log(
            "Student:",
            user.name
        );

    }
}

16. Discriminated Union

A discriminated union uses a common literal property to distinguish between different object types.

interface SuccessResponse {
    status: "success";
    data: string;
}

interface ErrorResponse {
    status: "error";
    message: string;
}

type Response =
    SuccessResponse |
    ErrorResponse;

function handleResponse(
    response: Response
) {

    if (
        response.status ===
        "success"
    ) {

        console.log(
            response.data
        );

    } else {

        console.log(
            response.message
        );

    }
}

17. Narrowing with null

A null check can narrow a value that may be null.

function showName(
    name: string | null
) {

    if (name !== null) {

        console.log(
            name.toUpperCase()
        );

    } else {

        console.log(
            "No name"
        );

    }
}

18. Narrowing with undefined

You can check for undefined before using an optional value.

function showPhone(
    phone: string | undefined
) {

    if (phone !== undefined) {

        console.log(
            phone
        );

    } else {

        console.log(
            "Phone not available"
        );

    }
}

19. Truthiness Narrowing

A truthiness check can narrow values that may be null, undefined, or another falsy value.

function showName(
    name: string | null
) {

    if (name) {

        console.log(
            name.toUpperCase()
        );

    } else {

        console.log(
            "No name"
        );

    }
}

20. Optional Chaining and Narrowing

Optional chaining can safely access a property when an object may be undefined.

interface User {
    name: string;
    address?: {
        city: string;
    };
}

function showCity(
    user: User
) {

    console.log(
        user.address?.city
    );

}

21. Narrowing with Custom Type Guard

A custom type guard is a function that tells TypeScript whether a value belongs to a specific type.

function isString(
    value: unknown
): value is string {

    return typeof value === "string";
}

const value: unknown =
    "Hello";

if (isString(value)) {

    console.log(
        value.toUpperCase()
    );

}

22. Narrowing unknown Type

The unknown type requires narrowing before many operations can be performed.

function showValue(
    value: unknown
) {

    if (typeof value === "string") {

        console.log(
            value.toUpperCase()
        );

    } else if (
        typeof value === "number"
    ) {

        console.log(
            value.toFixed(2)
        );

    }

}

23. Narrowing Object Properties

TypeScript can narrow object properties based on conditions.

interface User {
    name: string;
    age?: number;
}

function showUser(
    user: User
) {

    if (user.age !== undefined) {

        console.log(
            user.age.toFixed(0)
        );

    }

}

24. Narrowing Function Parameters

Function parameters with union types can be narrowed inside the function.

function calculate(
    value: number | string
) {

    if (typeof value === "number") {

        return value * 2;

    }

    return value.length;

}

console.log(
    calculate(10)
);

console.log(
    calculate("Hello")
);

25. Narrowing Array Values

Array values can also be narrowed when their type is a union.

function process(
    value: string | number[]
) {

    if (Array.isArray(value)) {

        value.forEach(
            number => {
                console.log(number);
            }
        );

    } else {

        console.log(
            value.toUpperCase()
        );

    }
}

26. Common Type Narrowing Mistake

A common mistake is using a method without first checking whether the value supports that method.

function show(
    value: string | number
) {

    // Incorrect:
    // console.log(
    //     value.toUpperCase()
    // );

    if (typeof value === "string") {

        console.log(
            value.toUpperCase()
        );

    }

}

Always narrow a union before using a type-specific operation.

27. When Should You Use Type Narrowing?

Type narrowing is useful whenever a value has multiple possible types or structures.

  • Working with union types
  • Handling API responses
  • Checking optional properties
  • Handling null and undefined
  • Processing unknown data
  • Working with different object structures
  • React and React Native props

28. Common Type Narrowing Techniques

TypeScript provides several ways to narrow types.

  • typeof
  • instanceof
  • in
  • Equality checks
  • Truthiness checks
  • Array.isArray()
  • Discriminated unions
  • Custom type guards
  • Null and undefined checks

29. Complete Practical Example

interface Student {
    type: "student";
    name: string;
    course: string;
}

interface Teacher {
    type: "teacher";
    name: string;
    subject: string;
}

type User =
    Student | Teacher;

function showUser(
    user: User
) {

    console.log(
        "Name:",
        user.name
    );

    if (user.type === "student") {

        console.log(
            "Course:",
            user.course
        );

    } else {

        console.log(
            "Subject:",
            user.subject
        );

    }

}

showUser({
    type: "student",
    name: "Amit",
    course: "TypeScript"
});

showUser({
    type: "teacher",
    name: "Rahul",
    subject: "JavaScript"
});

30. Type Narrowing Summary

Type narrowing allows TypeScript to determine a more specific type from a broader type.

function showValue(
    value: string | number
) {

    if (typeof value === "string") {

        console.log(
            value.toUpperCase()
        );

    } else {

        console.log(
            value.toFixed(2)
        );

    }
}

By narrowing types before using them, your code becomes safer and TypeScript can provide better type checking and autocomplete.

📌 Key Points

  • Type narrowing reduces a broad type to a more specific type.
  • It is especially useful with union types.
  • typeof can narrow primitive types.
  • instanceof can narrow class instances.
  • The in operator can narrow object types.
  • Equality checks can narrow literal and union values.
  • Array.isArray() can narrow arrays.
  • Null and undefined checks can narrow nullable values.
  • Discriminated unions are useful for related object structures.
  • Custom type guards can provide reusable narrowing logic.
  • Type narrowing is useful when working with unknown.
  • It helps TypeScript safely use type-specific properties and methods.

🧠 Quick Quiz

Question: Which operator is commonly used to narrow primitive types such as string and number?