Comparing TypeScript's Type Narrowing Techniques for Safer Code

Learn how TypeScript's type narrowing techniques help you write safer code by reducing errors and improving type checks. This beginner-friendly guide explains different narrowing methods with practical examples.

TypeScript is a powerful language that builds on JavaScript by adding static types. One of TypeScript's key features is type narrowing, where the compiler refines variable types based on runtime checks. This helps catch errors early and ensures safer, more predictable code. In this article, we'll explore common type narrowing techniques with simple examples to help you understand how to write safer TypeScript code.

### 1. typeof Narrowing TypeScript can narrow types based on the `typeof` operator, which works well for primitive types like `string`, `number`, and `boolean`.

typescript
function printValue(value: string | number) {
  if (typeof value === 'string') {
    console.log('String value:', value.toUpperCase());
  } else {
    console.log('Number value:', value.toFixed(2));
  }
}

printValue('hello'); // String value: HELLO
printValue(3.1415);  // Number value: 3.14

Here, TypeScript knows inside the `if` block that `value` is a `string` and in the `else` block it must be a `number`. This prevents errors like trying to call a string method on a number.

### 2. instanceof Narrowing You can narrow types with the `instanceof` operator, useful when working with classes or built-in objects.

typescript
class Dog {
  bark() {
    console.log('Woof!');
  }
}

class Cat {
  meow() {
    console.log('Meow!');
  }
}

function speak(animal: Dog | Cat) {
  if (animal instanceof Dog) {
    animal.bark(); // TypeScript knows 'animal' is Dog
  } else {
    animal.meow(); // TypeScript knows 'animal' is Cat
  }
}

speak(new Dog());
speak(new Cat());

Since the types are narrowed to either `Dog` or `Cat`, you can safely access the class-specific methods without errors.

### 3. Equality Narrowing Comparing a variable to a literal or another variable can narrow union types.

typescript
function checkStatus(status: 'success' | 'error' | 'loading') {
  if (status === 'success') {
    console.log('Data loaded successfully!');
  } else if (status === 'error') {
    console.log('Error loading data.');
  } else {
    console.log('Loading...');
  }
}

checkStatus('success');

The equality checks narrow type based on the matched literal, ensuring your code only handles the possible cases you specify.

### 4. in Operator Narrowing The `in` operator checks for keys in objects to help narrow down types when dealing with unions of object shapes.

typescript
type Square = { kind: 'square'; size: number };
type Rectangle = { kind: 'rectangle'; width: number; height: number };

type Shape = Square | Rectangle;

function area(shape: Shape) {
  if ('size' in shape) {
    return shape.size * shape.size;
  } else {
    return shape.width * shape.height;
  }
}

console.log(area({ kind: 'square', size: 5 }));    // 25
console.log(area({ kind: 'rectangle', width: 4, height: 6 })); // 24

The use of `'size' in shape` lets TypeScript narrow that `shape` is a `Square` and access `size` safely.

### 5. Custom Type Guards You can write your own functions to narrow types by returning a type predicate.

typescript
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();
  }
}

The function `isFish` is a user-defined type guard. It returns a boolean, but more importantly, tells TypeScript that the checked object should now be treated as a `Fish` inside the `if` block.

### Summary Type narrowing is essential for writing safe TypeScript code. Using built-in keywords like `typeof`, `instanceof`, `in`, equality checks, and custom type guards, you can help the compiler understand exactly what types your code is working with at different points, avoiding runtime errors and improving code clarity. Try these techniques in your next TypeScript project!