Programming Language
TypeScript
Variables and Datatypes
Type Narrowing

TypeScript Type Narrowing

Type narrowing is a process in TypeScript that allows you to narrow down the type of a variable within a conditional block. This helps TypeScript to understand the specific type of a variable at a given point in the code, enabling better type checking and code completion.

Example of Type Narrowing

Here's an example of type narrowing using the typeof operator:

function padLeft(value: string | number, padding: string | number) {
  if (typeof padding === "number") {
    return Array(padding + 1).join(" ") + value;
  }
  if (typeof padding === "string") {
    return padding + value;
  }
  throw new Error(`Expected string or number, got '${padding}'.`);
}

In this example, the padLeft function takes two parameters: value and padding, which can be either a string or a number. The typeof operator is used to narrow down the type of padding within the conditional blocks. If padding is a number, it pads the value with spaces. If padding is a string, it concatenates the padding with the value.

Other Type Narrowing Techniques

Besides the typeof operator, TypeScript provides other ways to narrow types:

  • instanceof operator: Used to check if an object is an instance of a class.
  • Type predicates: Custom functions that return a type predicate.
  • Discriminated unions: Using a common property to distinguish between different types in a union.

Example using instanceof:

class Dog {
  bark() {
    console.log("Woof!");
  }
}
 
class Cat {
  meow() {
    console.log("Meow!");
  }
}
 
function makeSound(animal: Dog | Cat) {
  if (animal instanceof Dog) {
    animal.bark();
  } else if (animal instanceof Cat) {
    animal.meow();
  }
}

In this example, the makeSound function uses the instanceof operator to narrow down the type of the animal parameter.

Example using Type Predicates:

interface Fish {
  swim: () => void;
}
 
interface Bird {
  fly: () => void;
}
 
function isFish(pet: Fish | Bird): pet is Fish {
  return (pet as Fish).swim !== undefined;
}
 
function move(pet: Fish | Bird) {
  if (isFish(pet)) {
    pet.swim();
  } else {
    pet.fly();
  }
}

In this example, the isFish function is a type predicate that narrows down the type of pet to Fish if it has a swim method.

Example using Discriminated Unions:

interface Square {
  kind: "square";
  size: number;
}
 
interface Circle {
  kind: "circle";
  radius: number;
}
 
type Shape = Square | Circle;
 
function getArea(shape: Shape) {
  switch (shape.kind) {
    case "square":
      return shape.size ** 2;
    case "circle":
      return Math.PI * shape.radius ** 2;
  }
}

In this example, the Shape type is a discriminated union with a common kind property.

Conclusion

Type narrowing is a powerful feature in TypeScript that enhances type safety and code readability by allowing you to work with more specific types within conditional blocks. By using techniques like typeof, instanceof, type predicates, and discriminated unions, you can write more robust and maintainable code.