DevAcademy
LearnTypeScriptType Narrowing
IntermediateTypeScript

Type Narrowing

Learn how TypeScript narrows a broad or union type down to a more specific one within conditional checks.

Reading Time

18 min

Lesson

Lesson 21 of 30

What is Narrowing?

Narrowing is how TypeScript deduces a more specific type for a value within a certain block of code, based on a runtime check you’ve already written — like an if statement.

typeof Narrowing

function format(value: string | number): string {
  if (typeof value === "string") {
    return value.toUpperCase(); // narrowed to string
  }
  return value.toFixed(2); // narrowed to number
}

Truthiness Narrowing

A simple truthy check can narrow out null, undefined, "", 0, and NaN, which is especially useful for optional values.

Truthiness Narrowing Example

function printLength(value?: string) {
  if (value) {
    console.log(value.length); // narrowed: value is 'string', not 'string | undefined'
  }
}

instanceof Narrowing

instanceof narrows a value based on which class it was constructed from — commonly used with union types made of different class instances.

instanceof Narrowing Example

class ApiError extends Error {
  constructor(public statusCode: number, message: string) {
    super(message);
  }
}

function handle(error: Error | ApiError) {
  if (error instanceof ApiError) {
    console.log(error.statusCode); // narrowed to ApiError
  }
}

in Narrowing

The in operator narrows based on whether an object has a particular property, useful for unions of plain object shapes that don’t share a class hierarchy.

in Narrowing Example

interface Cat { meow(): void; }
interface Dog { bark(): void; }

function makeSound(animal: Cat | Dog) {
  if ("meow" in animal) {
    animal.meow(); // narrowed to Cat
  } else {
    animal.bark(); // narrowed to Dog
  }
}

Discriminated Unions

When every member of a union shares a common literal property (a "tag"), checking that property narrows the entire object automatically — this is the most robust and scalable narrowing pattern.

Discriminated Union Narrowing

type Result =
  | { status: "success"; data: string }
  | { status: "error"; message: string };

function handleResult(result: Result) {
  if (result.status === "success") {
    console.log(result.data); // narrowed: 'data' exists on this branch
  } else {
    console.log(result.message); // narrowed: 'message' exists on this branch
  }
}

Custom Type Guards Extend Narrowing

When built-in checks aren’t enough, you can write your own type predicate function (covered in a later lesson) to teach TypeScript how to narrow a type based on your own custom logic.

Best Practice

Prefer discriminated unions over loosely related optional properties whenever a value can take a few distinct shapes — the narrowing is exhaustive, safe, and reads clearly at every call site.

Interview Questions

Quick Quiz

1. What does narrowing mean in TypeScript?

2. Which operator narrows a union based on whether an object has a specific property?

3. What makes a discriminated union safe and exhaustive to narrow?