Types vs. interfaces in TypeScript

January 15, 2023

TypeScript is a superset of JavaScript that has been increasingly used by the community. At the time of this post, we are already at version 4.9.4, which is bringing very interesting features to make our lives as developers easier.

Types and type aliases

Before we delve into the differences between types and interfaces in TypeScript, we need to understand something.

In TypeScript, we have many basic types, such as string, boolean, and number. These are the basic types of TypeScript. You can check the list of all basic types here. In addition, in TypeScript, we have advanced types, and in these advanced types, we have something called type aliases. With type aliases, we can create a new name for a type, but without defining a new type.

We use the type keyword to create a new type aliases, which is why some people may get confused and think that they are creating a new type when they are just creating a new name for a type. So when you hear someone talking about the differences between types and interfaces, as in this article, you can assume that that person is talking about type aliases vs interfaces.

Types vs. interfaces

The difference between types and interfaces in TypeScript used to be clearer, but with the latest versions of TypeScript, the two are becoming more similar.

Interfaces are basically a way of describing data shapes and can be an example of an object.

Main features

Let's look at the main features and functionalities of Types and Interfaces and compare their usage.

Interfaces

Ideal for defining the structure of an object or classes, interfaces are great for developing a project open to implementations and behavior extensions.

1interface Age {
2 (birthDate: string): number;
3}
4
5interface Person {
6 name: string;
7 getAge: Age;
8}
9
10const person: Person = {
11 name: "Maria",
12 getAge: (birthDate) => {
13 const age = new Date(birthDate).getFullYear() - new Date().getFullYear();
14 return -age;
15 },
16};

Can be implemented by classes

Interfaces can be used to help structure your classes by defining generic structures that can be reused.

1interface Person {
2 name: string;
3}
4
5class User implements Person {
6 name = "John Doe";
7}

Are extensible

You can extend interfaces in the declaration of other interfaces.

1interface Person {
2 name: string;
3}
4
5interface User extends Person {
6 address: string;
7}
8
9const user: User = {
10 name: "John Doe",
11 address: "Brazil",
12};

Allow declaration merging

Declaration merging is a way to extend an interface, but in a less explicit way. Note: This is not a recommended practice. In the code below, notice that TypeScript does not complain when creating two interfaces with the same name, which does not happen with Types.

1interface Person {
2 name: string;
3}
4
5interface Person {
6 age: number;
7}
8
9const person: Person = {
10 name: "John Doe",
11 age: 20,
12};

Type Aliases

By allowing you to create aliases for primitives, functions, and objects, type aliases are powerful tools that can expand the typing of your project to a very advanced level.

1type BirthDate = string;
2type Name = string;
3type Age = {
4 (birthDate: BirthDate): number;
5};
6
7type Person = {
8 name: Name;
9 getAge: Age;
10};
11
12const person: Person = {
13 name: "Maria",
14 getAge: (birthDate) => {
15 const age = new Date(birthDate).getFullYear() - new Date().getFullYear();
16 return -age;
17 },
18};

Allow Intersections and Unions.

One of the biggest functionalities of type aliases is that intersections and unions allow you to combine them in various ways.

  1. Intersections work the same way as extends that interfaces support, to use intersections we use the & operator.
  2. Unions work as an alternative to the main type, to use unions we use the | operator:

Intersections

1type Person = {
2 name: string;
3};
4
5type Young = {
6 hungry: boolean;
7};
8
9type User = Person & { address: string };
10type Me = Person & Young;
11
12const user: User = {
13 name: "John Doe",
14 address: "Mozambique",
15};
16
17const me: Me = {
18 name: "Vinícius",
19 hungry: true,
20};

Unions

1type Car = {
2 wheels: 4;
3};
4
5type Motorcycle = {
6 wheels: 2;
7};
8
9let vehicle: Car | Motorcycle = {
10 wheels: 4,
11}; // Car
12
13vehicle = {
14 wheels: 2,
15}; // Motorcycle
16
17vehicle = {
18 wheels: 1,
19}; // Error

Tuplas

Tuples are a very useful concept in TypeScript, bringing us this new type of data that includes two sets of values of different data types.

1type Response = [string, number];

But in TypeScript, we can only declare tuples using types and not interfaces. There is no way to declare a tuple in TypeScript using an interface, but you can still use a tuple inside an interface, like this:

1interface Response {
2 value: [string, number];
3}

Which one should I use?

This question is really complicated, and the answer to it can vary depending on what you are developing.

Interfaces are better when you need to define a new object or method of an object. For example, in React applications, when you need to define parameters that a specific component will receive, it is ideal to use the interface in addition to type:

1import { FunctionComponent } from "react";
2
3interface TodoProps {
4 name: string;
5 isCompleted: boolean
6};
7
8const Todo: FunctionComponent<TodoProps> = ({ name, isCompleted }) => {
9 ...
10};

Interface works better with objects and method objects, and types are better for working with functions, complex types, etc.

I hope you don't start using one and exclude the other. Instead, start refactoring slowly, thinking about what makes the most sense for that specific situation.

Remember that you can use both together and they will work well. The idea here is just to clarify the differences between types and interfaces, and the best use cases for both. I'm not saying that one replaces the other

Conclusion

In this article, we learned more about the differences between types and interfaces in TypeScript. We learned that type aliases are advanced types in TypeScript, learned the best use cases for types and interfaces, and how we can apply both in real projects.

Personally, I use them as follows:

  1. interface: define object and class structures.
  2. type: define functions, use more advanced features such as conditional types, type-guards, etc.
1// https://www.typescriptlang.org/docs/handbook/2/conditional-types.html
2interface Animal {
3 live(): void;
4}
5interface Dog extends Animal {
6 woof(): void;
7}
8
9type Example1 = Dog extends Animal ? number : string;
10// ^ = type Example1 = number
11
12type Example2 = RegExp extends Animal ? number : string;
13// ^ = type Example2 = string