Do you really know TypeScript? (3): Types and interfaces
Tags: javascript, programming, webdev, typescript
Published: 2022-01-02 10:52:25

One of the things that you will do the most with Typescript is to define the shape of objects with `type` or `interface`. For that reason, understanding both well will make your TypeScript better quickly.

Let's see their main differences aside of the syntax, common patterns and surprising behaviours.

## `type` can be used for more things
While `interface` is used just to define the shape of objects, `type` has other use cases.

```typescript
type Pet = 'Cat' | 'Dog'

type CoolNumbers = 3.1416 | 4 | 100
```
## Interface merging
You should be aware of this one.

```typescript
interface DesktopFile {
  icon: string;
}

interface DesktopFile {
  extension: string;
}

// Error: Property 'icon' is missing in type '{ extension: string; }' 
// but required in type 'DesktopFile'.
const file: DesktopFile = {
  extension: 'pdf',
}
```
It can be surprising that you can redeclare an interface and merge them!

This is also known as "interface augmenting" and can be desirable in some situations but is definitely unusual in other languages.

Note that using `Type` would result in an error.

## Discriminated union
Also known as "tagged union", is a frequent pattern in TypeScript.

It can be weird if you are used to polymorphism using classes, but since TypeScript's types disappear at runtime, you need to do things a bit different.

```typescript
type File = {
    kind: 'file';

    name: string;
    extension: string;
}

type Folder = {
    kind: 'folder';

    name: string;
    filesInside: number;
}

type DesktopItem = File | Folder

const item: DesktopItem = {...}

if (item.kind === 'file'){
    // TypeScript knows that the properties
    // of the type File are defined here
}
``` 

This can be used like `instanceof` in other languages.

## Union of types vs types of unions
Generally prefer **union of types**.

```typescript
type Vehicle = {
    kind: 'motorcycle' | 'car'

    numberOfWheels: number 
    numberOfAirbags: number | undefined
}

const vehicle: Vehicle = {...}

if (vehicle.kind === 'car'){
    // TypeScript still thinks that
    // numberOfAirbags could be undefined
}

```

If we used union of types instead, like in the "discriminated union" example, TypeScript can be sure that the `Car` properties are available.

## Excess property checking

This is a mechanism that can mess your mental model of structural typing when using `type` and `interface`.

```typescript
interface Cat {
  name: string;
  whiskersLength: number;
}
const cat: Cat = {
  name: 'Uxia',
  whiskersLength: 6,
  bestFriend: 'Nina',
// ~~~~~~~~~~~~~~~~~~ Object literal may only specify known properties,
//                    and 'bestFriend' does not exist in type 'Cat'
};
```
From a structural typing point of view it is valid as the defined object contains at least the properties declared for `Cat`.

This is excess property checking complaining though. 

Check out this case:

```typescript
type Person = {
    name: string;
    zipCode?: string;
}

const randomGuy: Person = {
    name: 'Pedro',
    zip: '45420',
}
```

Excess property checking quickly points out an error that we could've spent too much time looking for otherwise.

Note that this check only happens when using object literals.

## Should I use `type` or `interface` then?

I find `type` easier to reason about and more readable. 

One exception would be when extending types:
```typescript
type Flyable = {
  fly(): void;
}

type Airplane = Flyable & {
  ...
}

interface Helicopter extends Flyable {
  ...
}
```

Also, as we saw earlier, "interface augmenting" can be unfamiliar to many people.

Be aware of their differences, try to make your team agree in their uses for the sake of consistency and you will be fine.

## Resources to go deeper
- [Effective TypeScript](https://www.goodreads.com/en/book/show/48570456-effective-typescript)
- [TypeScript docs](https://www.typescriptlang.org/docs/)