TypeScript Type Narrowing: A Comprehensive Guide 🤖
Tags: webdev, typescript
Published: 2023-12-20 01:20:16

TypeScript provides a powerful feature called "type narrowing" that allows you to make your code more precise by narrowing down the type of a variable within a certain block of code.

This feature helps you write safer and more maintainable code by leveraging TypeScript's static type checking.

In this article, we'll explore the basics of type narrowing and gradually delve into more advanced scenarios.


**Basic type narrowing**

`typeof` Narrowing

```typescript
function printMessage(message: string | number) {
  if (typeof message === 'string') {
    // Within this block, TypeScript knows that `message`
    // is a string
    console.log(message.toUpperCase());
  } else {
    // Here, TypeScript knows that `message` is a number
    console.log(message.toFixed(2));
  }
}

printMessage('Hello'); // Output: HELLO
printMessage(42);      // Output: 42.00
```


**User-Defined Type Guards aka ( T : is K )** 

```typescript
interface Cat {
  type: 'cat';
  meow(): void;
}

interface Dog {
  type: 'dog';
  bark(): void;
}

function isCat(animal: Cat | Dog): animal is Cat {
  return animal.type === 'cat';
}

function handleAnimal(animal: Cat | Dog) {
  if (isCat(animal)) {
    // TypeScript now knows that `animal` is a Cat
    animal.meow();
  } else {
    // TypeScript knows that `animal` is a Dog
    animal.bark();
  }
}

```

**Discriminated Unions** Aka additional `prop:value`

This sounds a bit sophisticated but it's very simple.


```typescript
type Circle = { kind: 'circle', radius: number }
type Rectangle = { kind: 'rectangle'; width: number; height: number };
type Shape = Circle | Rectangle;

function getArea(shape: Shape): number {
  switch (shape.kind) {
    case 'circle':
      // TypeScript knows `shape` is a circle here
      return Math.PI * Math.pow(shape.radius, 2);
    case 'rectangle':
      // TypeScript knows `shape` is a rectangle here
      return shape.width * shape.height;
  }
}

```

Here by adding the attribute `kind` we are telling typescript
what type we are expecting.

don't get intimidated by this one, in real life apps this can be one of these like 
`__type__: something` or `__typename` if you are familiar with graphql



**`instance of` type guard**

```typescript
class Car {
  drive() {
    console.log('Vroom!');
  }
}

class Bike {
  ride() {
    console.log('Ring ring!');
  }
}

function handleVehicle(vehicle: Car | Bike) {
  if (vehicle instanceof Car) {
    // TypeScript knows now `vehicle` is an instance of Car
    vehicle.drive();
  } else {
    // TypeScript knows `vehicle` is an instance of Bike
    vehicle.ride();
  }
}
```

**`is` type guard**

```typescript
interface Fish {
  swim(): void;
}

interface Bird {
  fly(): void;
}

function isFishOrBird(pet: Fish | Bird): pet is Fish {
  return 'swim' in pet;
}

function handlePet(pet: Fish | Bird) {
  if (isFishOrBird(pet)) {
    // TypeScript knows `pet` is a Fish
    pet.swim();
  } else {
    // TypeScript knows `pet` is a Bird
    pet.fly();
  }
}
```

**Bonus the `extends` utility**

In contrast of type narrowing and how we can use it to tell
typescript how to understand our types.
There are instances we want to use generics


```typescript
// Base type with a timestamp property
type TimestampedObject = {
  timestamp: Date;
};

// Generic function that works with timestamped objects
// by telling typescript T needs to be an object that has a
// timestamp property where timestamp is a Date type
function processTimestampedObject<T extends TimestampedObject>(obj: T): void {
  // Access the timestamp property safely
  const timestamp: Date = obj.timestamp;

  // Perform some processing with the timestamp
  console.log(`Processing timestamp: ${timestamp.toISOString()}`);
}
```

Now if you try to run the previous code

```typescript
// This works fine because the timestamp property is a Date
const validObject: TimestampedObject = { timestamp: new Date() };
processTimestampedObject(validObject);

// This would cause a TypeScript error without using `extends`
const invalidObject: TimestampedObject = { timestamp: '2022-01-01' };
processTimestampedObject(invalidObject);
```

**Conclusion**

Incorporating these techniques into your codebase will lead to safer and more predictable TypeScript applications.


**Also checkout**

[10 typescript utilities you need to know](https://dev.to/hichembenchaabene/10-typescript-utilities-you-need-to-know-bgn)

If you find this useful please hit the 👍 button
Cheers