DEV Community

Cover image for Stop Using TypeScript Interfaces
Afzal Imdad
Afzal Imdad

Posted on • Updated on

Stop Using TypeScript Interfaces

Why You Should Use Types Instead

This image has been generated by AI

Join our Vibrant Discord Community 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
}
Enter fullscreen mode Exit fullscreen mode

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",
}
Enter fullscreen mode Exit fullscreen mode

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 }
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

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 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.

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" }
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (31)

Collapse
 
fyodorio profile image
Fyodor

IMO the described criteria are not enough to say “stop using” 😅 I’d say, use interfaces for object-oriented code and types for more functional codebases. And as usual, consistency importance prevails over specific choice.

Collapse
 
papilocloud profile image
papilo-cloud

My thoughts though 😃

Collapse
 
raunor profile image
Ville Uksi

STOP posting STOP posts if you are not aware of the topics you're ranting about. Interfaces and types have clear distinction on use cases and both are essential for TS.

Collapse
 
wintersunset95 profile image
Winter

💯

Collapse
 
szeredaiakos profile image
szeredaiakos

NO!
Interfaces are interfaces and type definitions are type definitions. You should probably dig into the history of both before starting another "best practices" scam.

Collapse
 
bwca profile image
Volodymyr Yepishev

Alternatively one could stick to what TypeScript team recommends. Always type over interface seems like religious fundamentalism 💁

Collapse
 
zeeh1975 profile image
zeeh1975

"For the most part, you can choose based on personal preference, and TypeScript will tell you if it needs something to be the other kind of declaration. If you would like a heuristic, use interface until you need to use features from type."

Collapse
 
wintersunset95 profile image
Winter

STOP using CLICKBAIT titles

Collapse
 
matthewjheaney profile image
Matthew Heaney

The argument that you should stop using interfaces because of declaration merging is completely bogus, since use of this typescript feature is extremely rare, and it's doubtful more than a few typescript programmers even know about it.

At Google we're required to use interfaces instead of types. If you do use a type, it gets flagged by the TS linter and the presubmit check would fail.

In practice types and interfaces are complimentary language features, and sometimes for advanced type manipulation, these features must be used in combination.

Collapse
 
bwca profile image
Volodymyr Yepishev

How do you handle generics that produce mapped types with mere interface if it's linter flagged? Do you just write new interface instead? 🤔

Collapse
 
matthewjheaney profile image
Matthew Heaney

The rule at Google is, don't use a type if you can use an interface. Of course this doesn't rule out all use of type-style declarations. Really, it just boils down to this:

type T = {n: number};
vs
interface I {n: number}

The latter is allowed, the former is not.

Collapse
 
pwnorris profile image
Pwn Norris • Edited

Imagine being a "Person" but also being "Work"...
You'll soon be a "Person" implementing "Unemployed" if your employer sees this post!

Correction: According to my wife, I am indeed, a piece of "Work"...

Collapse
 
afzalimdad9 profile image
Afzal Imdad

Subject: Apology for Posting Incomplete Blog Content

Dear Esteemed Blog Readers,

I hope this message finds you well. I am writing to extend my sincerest apologies for the recent oversight regarding the incomplete documentation in the blog post I shared with you all. It was an error on my part to neglect thorough verification before posting, and I deeply regret any inconvenience or confusion it may have caused.

I understand the importance of providing accurate and comprehensive information to our readers, and I take full responsibility for falling short of this standard. Please know that this oversight does not reflect the values or commitment of our blog team.

I want to assure you that I am taking immediate steps to rectify this situation. I will diligently review the complete documentation and replace the incomplete content with accurate and detailed information within the next few days.

Your trust and engagement are of utmost importance to me, and I am dedicated to ensuring that our blog maintains the highest quality standards. Your understanding and patience during this time are greatly appreciated.

If you have any further questions or concerns, please do not hesitate to reach out to me directly. Thank you for your continued support and readership.

Warm regards,

Afzal Imdad

Collapse
 
chobotx profile image
ChobotX • Edited

I found out that the best for our team is to use types for data and interfaces for function parameters and vue component props.. This way we achieve both dependency inversion and interface segregation in our vue/ts cosebase.

eg.:

type Person = {
    name: string;
    surname: string;
    age: number;
};

interface Named {
    name: string;
    surname: string;
}

const getFullName = (input: Named): string => {
    return input.name + ‘ ‘ + input.surname;
};
Enter fullscreen mode Exit fullscreen mode

Function doesn’t and shouldn’t need to know anything about age.

The difference for us is simple. Type is always complete, interface might and probably is only partial.

Collapse
 
jackd5011 profile image
Jack Davenport

That last line isn’t necessarily true. Types and interfaces both have support for optional parameters, and obviously at runtime it’s possible for any of the parameters to be undefined.

All you’re doing is inexplicitly creating an extended interface/intersection type.

Collapse
 
chobotx profile image
ChobotX

I might missunderstood you, but my example is exactly the opposite I think.

It’s not about extending ‘Named’ with ‘age’ yet about ommiting ‘age’ from ‘Person’ without any direct dependency on it. So you can use ‘getFullName’ for types ‘Person’, ‘Cat’ or even a ‘Car’ as long as it satisfies ‘Named’ interface 🤔

Sure you can take it the other way and make your data be an intersection type of the component/function types it’s used at, but then there is no segregation..

Am I missing something?

Collapse
 
mattpocockuk profile image
Matt Pocock

Thanks for the shout-out, but there is a lot that's incorrect in this article.

Classes can implement types.
Interface extensions are significantly faster for tsc to parse than intersections. Types and interfaces are the same, but the way you extend them matters.
There is no 'order of precedence' with declaration merging.

I still agree that types are preferred until you need a feature of interfaces, but I don't think this article has enough fact in it to be used as a reference.

Collapse
 
jackd5011 profile image
Jack Davenport

Hello Matt 👏

Collapse
 
jason_espin profile image
Jason Espin

Please do not write clickbait articles like this when you do not know what you are talking about. It is misleading for junior developers who do not know any better. To anyone reading the comments, ignore this article and instead follow the guidelines laid out by the Typescript team.

Collapse
 
jackd5011 profile image
Jack Davenport

Tbh I feel like article does a better job of convincing you why you should use interfaces rather than shouldn’t.

The main drawback seems to be declaration merging and while that could cause issues, it’s pretty unlikely to happen in a well managed codebase, so I don’t think the argument is really strong enough.

For my projects I use interfaces primarily for defining object shapes, and then types for creating union types or extending existing types from libraries. They both have valid uses in most Typescript codebases.

Collapse
 
digitalhallucinations profile image
DigitalHallucinations

Damn you got shut down… and rightly so

Collapse
 
matheus_1298c951919b20c1f profile image
Matheus • Edited

There are two types of people here: Pricks trying to bash you for saying your opinion (even if you said some wrong things), and people who're giving constructive feedback.

To the first group of people: fuck you.

This article helped me to make my decision and to understand better where i can use types or interfaces. In the context of React with functional components, i would 100% prefer to use types (where in other situations, i would switch back to interfaces). There are a lot of sitatuations (some of them they just one or another, for the sake of following a pattern), and that's okay.

Collapse
 
yutakusuno profile image
Yuta Kusuno • Edited

Especially, in large code bases, the performance of the interface is better than that of the type in some cases.
github.com/microsoft/TypeScript/wi...