DEV Community

Cover image for TypeScript: Interfaces vs Types - Understanding the Difference
Anton Martyniuk
Anton Martyniuk

Posted on • Originally published at antondevtips.com on

TypeScript: Interfaces vs Types - Understanding the Difference

This blog post makes a deep dive into TypeScript's object type system, exploring interfaces and types, difference between them with various examples to help you master these types.

Originally published at https://antondevtips.com.

What Are Interfaces and Types In TypeScript

Interfaces and types belong to the object types in TypeScript.

Object types in TypeScript groups a set of properties under a single type.

Object types can be defined either using type alias:

type User = {
    name: string;
  age: number;
  email: string;
};

const user: User = {
  name: "Anton",
  age: 30,
  email: "info@antondevtips.com"
};
Enter fullscreen mode Exit fullscreen mode

Or interface keyword:

interface User {
  name: string;
  age: number;
  email: string;
}

const user: User = {
  name: "Anton",
  age: 30,
  email: "info@antondevtips.com"
};
Enter fullscreen mode Exit fullscreen mode

Interfaces in TypeScript

Interfaces are extendable and can inherit from other interfaces using the extends keyword:

interface Employee extends User {
  employeeId: number;
  salary: number;
}

const employee: Employee = {
  name: "Jack Sparrow",
  age: 40,
  email: "captain.jack@gmail.com",
  employeeId: 1,
  salary: 2000
};
Enter fullscreen mode Exit fullscreen mode

Classes in TypeScript can inherit from interfaces and implement them using the extends keyword.

You can also extend interfaces by declaring the same interface multiple times:

interface User {
  name: string;
  age: number;
  email: string;
}

interface User {
  phone: string;
}

const user: User = {
  name: "Anton",
  age: 30,
  email: "info@antondevtips.com",
  phone: "1234567890"
};
Enter fullscreen mode Exit fullscreen mode

In TypeScript interfaces, you can use property modifiers to define optional and readonly properties.

Optional Properties

These properties are not mandatory when creating an object:

interface User {
  name: string;
  age: number;
  email?: string; // Optional property
}

const user: User = {
  name: "Anton",
  age: 30
};
Enter fullscreen mode Exit fullscreen mode

Readonly Properties

These properties cannot be modified after the object is created:

interface User {
  name: string;
  readonly age: number;
  email: string;
}

const user: User = {
  name: "Anton",
  age: 30,
  email: "john.doe@example.com"
};

// Error: Cannot assign to 'age' because it is a read-only property.
// user.age = 31;
Enter fullscreen mode Exit fullscreen mode

Index Signatures

Index signatures allow you to define the type of keys and values that an object can have.

Sometimes you may not know all the names of the properties during compile time, but you do know the shape of the values.
In such use cases you can use an index signature to define the properties:

interface Names {
  [index: number]: string;
}

const names: Names = ["Anton", "Jack"];
Enter fullscreen mode Exit fullscreen mode

You can also define dictionaries by using index signatures:

interface NameDictionary {
  [key: string]: string;
}

const dictionary: NameDictionary = {
  name: "Anton",
  email: "info@antondevtips.com"
};
Enter fullscreen mode Exit fullscreen mode

There is a limitation when working with index signatures: you can't have named properties of another type:

interface NumberDictionary {
  [index: string]: number;

  length: number;

  // Error: Property 'name' of type 'string' is not assignable to 'string' index type 'number'.
  name: string;
}
Enter fullscreen mode Exit fullscreen mode

You can bypass this limitation my using Unions:

interface Dictionary {
  [index: string]: number | string;
  length: number;
  name: string;
}

const dictionary: Dictionary = {
    name: "Anton",
    email: "info@antondevtips.com",
    length: 5
};
Enter fullscreen mode Exit fullscreen mode

It might be useful to make index signatures readonly to prevent assignment to their indexes:

interface ReadonlyDictionary {
  readonly [index: number]: string;
}

const dictionary: ReadonlyDictionary = {
    0: "Anton"
};

dictionary[0] = "John";
// Error: Index signature in type 'ReadonlyDictionary' is readonly.
Enter fullscreen mode Exit fullscreen mode

Types in TypeScript

The type in TypeScript is similar to the interfaces but you can't extend existing types or add new properties to them.

type User = {
  name: string;
  age: number;
  email: string;
};

type User = {
  phone: string;
};

// Error: Duplicate identifier 'User'.
Enter fullscreen mode Exit fullscreen mode

Combining Interfaces and Types with Intersection Types

Intersection types in TypeScript allow you to combine multiple types into one.
An intersection type can be created by combining multiple interfaces or types using & operator:

interface Person {
    name: string;
}

interface ContactDetails {
    email: string;
    phone: string;
}

type Customer = Person & ContactDetails;

const customer: Customer = {
    name: "Anton",
    email: "info@antondevtips.com",
    phone: "1234567890"
};
Enter fullscreen mode Exit fullscreen mode

In these examples a Customer type is an intersection type that combines all properties from Person and ContactDetails interfaces.

It's important to note that you can only declare an Intersection Type with a type keyword.
You can't declare an interface type that holds an Intersection.

Intersections are not limited just to interfaces and types, they can be used with other types, including primitives, unions, and other intersections.

Combining Interfaces and Types with Union Types

Union type in TypeScript is a type that is formed from two or more other types.
Union types are also called Discriminated Unions.

A variable of union type can have one of the types from the union.

Let's explore an example of geometrics shapes, that have common and different properties:

interface Circle {
    type: "circle";
    radius: number;
}

interface Square {
    type: "square";
    square: string;
}

type Shape = Circle | Square;
Enter fullscreen mode Exit fullscreen mode

Let's create a function that calculates a square for each shape:

function getSquare(shape: Shape) {
    if (shape.type === "circle") {
        return Math.PI * shape.radius * shape.radius;
    }

    if (shape.type === "square") {
        return shape.square;
    }
}

const circle: Circle = {
    type: "circle",
    radius: 10
};

const square: Square = {
    type: "square",
    size: 5
};

console.log("Circle square: ", getSquare(circle));
console.log("Circle square: ", getSquare(square));
Enter fullscreen mode Exit fullscreen mode

Here a getSquare function accepts a shape parameter that can be one of 2 types: Circle or Square.
We need to define a property that will allow to distinguish types from each other.
In our example it's a type property.

It's important to note that you can only create a Union Type with a type keyword.
You can't declare an interface type that holds a Union Type.

Difference Between Interfaces and Types

Interfaces:

  • can inherit from other interfaces
  • can merge declarations, which is useful for extending existing objects
  • can't represent more complex structures, like unions and intersections

Types:

  • doesn't support inheritance
  • doesn't allow extending existing objects
  • can represent more complex structures, like unions and intersections

Summary

Both Interfaces and Types in TypeScript are quite the same but have some differences.
Interfaces allow extension of existing objects, while types - not.
On the other hand Types can represent unions and intersections.

Use Interfaces when you need to define the structure of an object and take advantage of declaration merging and extension.
Use Types when you need you don't need them to be extended or to define complex types, such as unions or intersections.

There is no right choice whether to prefer Interfaces to Types or vice versa.
You can choose based on the needs and personal preference, the most important part is to select a single approach that will be consistent in the project.
My personal choice is Types as they can't be accidentally re-declared and extended, unless I need to do some fancy object-oriented stuff with interfaces and classes.

P.S.: you can find an amazing Cheat Sheets on Types and Interfaces from the Official TypeScript website.

Hope you find this blog post useful. Happy coding!

Originally published at https://antondevtips.com.

After reading the post consider the following:

  • Subscribe to receive newsletters with the latest blog posts
  • Download the source code for this post from my github (available for my sponsors on BuyMeACoffee and Patreon)

If you like my content —  consider supporting me

Unlock exclusive access to the source code from the blog posts by joining my Patreon and Buy Me A Coffee communities!

Top comments (0)