Not a Member? Read for FREE here.
Not a Member? Read for FREE here.
Types and Interfaces are crucial elements utilized within every TypeScript application.
But given that types and interfaces have very similar roles, it raises an interesting question: Which is better?.
Today, we will explore the different aspects of types and interfaces, and eventually conclude why you should generally prefer using types in most cases.
So without further ado… Let’s dive right in.
Let's break down the Person
type and interface specification.
type Person = {
name: string
age: number
}
interface Person {
name: string
age: number
}
It’s evident that types and interfaces share a similar syntax. However, a major distinction is that types use =
to specify the structure of an object, whereas interfaces do not.
Yet, there's a lot more involved in this...
Let’s dive in and take a closer look at different types and interfaces together.
Extensibility
When it comes to extensibility, numerous people believe that interfaces clearly come out on top since interfaces can expand 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
the Job
interface, so the properties of the Job
interface merge into the Person
interface.
Conversely, types provide flexibility by utilizing the union |
or intersection &
operators to combine existing types.
Interfaces aren't able to directly demonstrate this behavior.
// ✅ Works
type Person = {
name: string
age: number
} & { job: string }
// ❌ Does not work
interface Person {
name: string
age: number
} & { job: string }
Implementation
Interfaces in TypeScript work seamlessly with Object Oriented Programming (OOP), similar to other languages such as Java or C#.
This implies that interfaces can be used within classes by applying the implements
keyword.
Let’s go ahead and define Person
as a class
. We'll also create a new interface called Work
and ensure the agreement between them is fulfilled.
// 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 frequently use OOP, you'll find that interfaces are more relevant than types, since classes cannot directly implement types.
Performance
When discussing performance, we specifically refer to the performance of “type-checking” carried out by the TypeScript compiler. This performance tends to drop significantly as your codebase grows larger.
That's why we evaluate which types or interfaces excel in type-checking performance.
Check out this video where Matt Pocock discusses the distinctions between types and interfaces. He also clarifies that there is ZERO difference in type-checking performance between them.
In TypeScript, interfaces possess a special capability known as Declaration Merging.
Declaration merging occurs when the TypeScript compiler combines two or more interfaces that share the same name 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 capability enables easy enhancement and expansion of current interfaces without altering other dependencies.
Here’s an instance where I redefine the @auth/core/types
module and enhance the Session
interface.
This illustrates declaration merging as I enhance the original interface by adding a new id: string
attribute.
This is a justifiable scenario for using interfaces because it simplifies the process for developers to expand library interfaces.
Types do not allow this because they cannot be changed once they are created.
Conversely, declaration merging might negatively impact your codebase in unexpected ways for these two key reasons:.
- Order of Precedence: Subsequent declarations will always override earlier ones. If you’re not cautious, this might result in unforeseen complications when declaration merging happens throughout your program.
- Unsafe Merging with Classes: Because the TypeScript compiler doesn't verify whether properties are initialized, this can result in unforeseen runtime issues.
Types do not encounter this issue, making them more direct and safe to utilize.
Unless you need a certain interface behavior, such as for extensible refinement or implementing with OOP, your best option is to stay with types.
Types are adaptable, easy to use, and steer clear of the complications that come with declaration merging.
Types offer similar performance to interfaces, giving you another reason to choose types over interfaces in your codebase.