Type Or Interface in TypeScript?

April 03, 2019

In TypeScript, we can define our own “types” with either “type aliasing” or “interface”. So when to choose one over the other, and what are the real differences between them? Let’s explore it in this article!

The Twins

Let’s have a look at this code snippet, in which we define a new button “type” with one properties and one method -

type ButtonType = {
  isPrimary: boolean;
  onPress(): void;
}

interface IButton {
  isPrimary: boolean;
  onPress(): void;
}

Other than the minor syntax differences when defining the types, because of the structural typing nature of TypeScript, ButtonType and IButton are functionally equivalent here as they have exactly the same shape and structure.

In the next code snippet, we define a simple newPrimaryButton function which in principle should return an object of ButtonType type alias. However, when we assign its return value to an IButton interface, everything still works with no problems at all.

const newPrimaryButton = (): ButtonType => {
  return {
    isPrimary: true,
    onPress: () => {
      console.log("Pressed!");
    }
  };
};

const primaryButton: IButton = newPrimaryButton();

primaryButton.onPress(); // "Pressed!" with no errors whatsoever!

So, if the “type” keyword and “interface” are interchangeable in this scenario, when do they actually differ from each other?

Object Oriented? Use Interface!

This might sound like a no brainer - when we are doing proper OOP, then interface is our friend.

Just like in many other OOP languages, TypeScript allows a class to implement an interface, or extend (multiple) interfaces. Although, implementing or extending a type is also allowed, it’s best to just use interfaces for a better style.

One thing to notice which is only possible with interface is that we can have multiple interface definitions under the same name. The properties will merge under that same name, as if we have one interface defined in small chunks.

interface IButton {
  isPrimary: boolean;
}

interface IButton {
  onPress(): void;
}

// `IButton` interface as a whole now has both the boolean 
// property and the void method

If there is a conflict in some property’s type though, an compiler error will occur.

interface IButton {
  isPrimary: boolean;
}

interface IButton {
  isPrimary: number; // this is not allowed!
  onPress(): void;
}

Type for Aliasing

The “type” keyword is good for aliasing things. This is particularly useful when we are not really constructing a new type from scratch, but already have something verbose in hand that we want to create a handy alias for.

For example, if we have already defined ButtonWithAnimation and StaticButton individually, but would like to refer to “either one” of them later, we can create a “union” under the name Button with the type keyword.

type Button = ButtonWithAnimation | StaticButton;

const buttonBuilder = (isStatic: boolean): Button => {
  if (isStatic) {
    return new StaticButton();
  } else {
    return new ButtonWithAnimation();
  }
}

In other scenarios, we can also give a built-in type a new name, so that the code becomes much cleaner and easier to read.

type Indices = number[];

const x: Indices = [3, 7, 15, 44.3];

Using type is particularly a good idea for doing functional programming, especially that we can alias types and create unions or intersections of them conveniently.

Summary

Defining a new type with interface or type keywords really don’t differ a lot in either syntax or functionalities. However, choosing the right one to use is always a good idea to maintain a clean and readable code base.