Let’s imagine we have the following TypeScript interfaces:

export interface Animal {
  weight: number;
}

export interface Bird extends Animal {
  wings: number;
  fly: Function;
}

export interface Fish extends Animal {
  fins: number;
  swim: Function;
}

Our code deals with all kinds of animals so all our functions look more or less like so:

function observe(animal: Animal) {
  // ...
}

There’s an issue though. What if we need to know specifically whether we have a Bird? While checking for animal.wings is perfectly valid JavaScript, TypeScript errors out with Property 'wings' does not exist on type 'Animal'. Which makes sense: the Animal interface only declares a weight property.

The solution, then, is to use what’s called a Union Type:

function observe(animal: Bird | Fish) {
  // ...
}

A union type describes a value that can be one of several types. Here animal can either be a Bird or a Fish. And since a Bird has wings TypeScript won’t complain if we make a check for that property.

However, this has some drawbacks:

  1. The type declaration is wider, affecting the shape of the code.
  2. Adding a new animal classification like Reptile will potentially require changing a lot of type declarations.

What should we do then?

Well, we can hide (not export) the base interface and export Animal as a union of all animal classification types:

interface BaseAnimal {
  weight: number;
}

export interface Bird extends BaseAnimal {
  wings: number;
  fly: Function;
}

export interface Fish extends BaseAnimal {
  fins: number;
  swim: Function;
}

export type Animal = Bird | Fish;

Now to add Reptile we would only need to update the Animal export.


As an added benefit, this technique allows TypeScript to better infer types.

Whereas before we had to do this:

let animal: BaseAnimal;

if ('wings' in animal) {
  (animal as Bird).fly(); // TypeScript will throw an error unless animal is cast to Bird.
}

Now we can do this:

let animal: Animal;

if ('wings' in animal) {
  animal.fly(); // TypeScript already knows animal is a Bird!
}

Pretty neat.