Stop Using TypeScript Interfaces
Tags: javascript, webdev, programming, typescript
Published: 2024-06-09 07:40:26

## Why You Should Use Types Instead


![This image has been generated by AI](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tgfhiafk0sp30u93xi39.png)

Join our [Vibrant Discord Community](https://discord.com/invite/C2PXBMqpuV) for exclusive information and insightful discussions

**Types and Interfaces** are profound features used inside every TypeScript program.

However, since types and interfaces are quite similar in function, it begs the question: _**Which is better?**_

Today, we will evaluate types and interfaces, then reach a conclusion as to why you should use types over interfaces in most scenarios.

**So without further ado… Lets dive right in.**

## **So What Are The Differences?**

Let’s analyze this `Person` **type** and **interface** definition:

```
type Person = {
  name: string
  age: number
}
interface Person {
  name: string
  age: number
}
```

It is clear types and interfaces have similar syntax, with the key difference being that the type uses `=` to define the shape of an object unlike the interface.

However, there is much more to it than this.

Let’s dig deeper to explore and evaluate types and interfaces together.

## **Extensibility**

In terms of extensibility, many argue interfaces are obvious winners since interfaces may extend other interfaces using `extends`.

```
// Extensibility Example
interface Person extends Job {
  name: string
  age: number
}
interface Job {
  job: string
}
// Properties of Person & Job used.
const person: Person = {
  name: "John",
  age: 25,
  job: "Full-Stack Web Developer",
}
```

Here the `Person` interface `extends` `Job`, and as a result the properties of the `Job` interface merge into `Person`.

On the other hand, types also offer extensibility by leveraging the **union** `|` or **intersection** `&` operators to merge existing types.

**Interfaces cannot express this behavior directly.**

```
// ✅ Works 
type Person = {
  name: string
  age: number
} & { job: string }
// ❌ Does not work 
interface Person {
  name: string
  age: number
} & { job: string }
```

## **Implementation**
Interfaces in TypeScript are compatible with Object Oriented Programming (OOP) like in other languages, e.g. **Java** or **C#**.

This means interfaces can be implemented in classes using `implements`.

Let’s now define `Person` as a `class`, and implement a new interface called `Work` and satisfy the contract between them.

```
// Implementation Example
interface Work {
  doWork: () => void
}
class Person implements Work {
  name: string
  age: number
  constructor(name: string, age: number) {
    this.name = name
    this.age = age
  }
  // doWork method implemented to satisfy the `Work` interface.
  doWork() {
    console.log("Working...")
  }
}
const person = new Person("John", 25)
person.doWork()
```

Therefore if you use OOP frequently, interfaces will be more applicable than types, as types cannot be directly implemented by classes.

## **Performance**

When it comes to performance, we are talking about the performance of **“type-checking”** done by the TypeScript compiler, which decreases exponentially as your codebase increases in size.

This is why we benchmark which of types or interfaces are superior in terms of type-checking performance.

Here is a video where [Matt Pocock](https://www.mattpocock.com/) explains the differences between types and interfaces, and why there is actually **ZERO** difference in type-checking performance between types and interfaces.

Types vs Interfaces: 0 Performance Difference

## **Why Interfaces Could be Harmful**

Interfaces in TypeScript have a unique feature called [Declaration Merging](https://www.typescriptlang.org/docs/handbook/declaration-merging.html).

Declaration merging is when the TypeScript compiler merges **two or more** interfaces with identical names into **one**.

```
// Initial Person Interface
interface Person {
  name: string
  age: number
}
// Refining the Person interface using "Declaration Merging"
interface Person {
  gender: string
}
// Using the "Merged" interface to define a new "person"
const person: Person = { name: "John", age: 25, gender: "Male" }
```

On the one hand, this feature allows for convenient refinement and extension of existing interfaces without changing other dependencies.

Here is an example of me re-declaring the `@auth/core/types` module and refining the `Session` interface.

Refining the @auth/core interface

This is an example of **declaration merging** because I refine the original interface with a new `id: string` attribute.

This is a **justifiable** use case for interfaces because it allows developers to extend library interfaces with ease.

**Types do not permit this since they are immutable after creation.**

On the other hand, declaration merging can have detrimental and surprising effects on your codebase for these two main reasons:

1. **Order of Precedence:** Later declarations always take precedence of prior ones. If not careful, this could lead to unexpected issues when declaration merging occurs in many parts of your program.

2. **Unsafe Merging with Classes:** Since the TypeScript compiler doesn’t check for property initialization, this could lead to unexpected runtime errors.
Types do not have this problem, and hence are more straightforward and safe to use as a result.

## **Conclusion**

Unless specific interface behaviour is necessary, e.g. extensible refinement or implementation using OOP, your best bet is to stick with **types**.

Types are flexible, straightforward, and avoid pitfalls associated with **declaration merging**.

Types are also identical in performance compared to interfaces, providing you another reason to select types over interfaces in your codebase.