DEV Community

Cover image for TypeScript: type vs interface
Udayan Maurya
Udayan Maurya

Posted on

TypeScript: type vs interface

Let's resolve this forever outstanding question:
Should I use type or interface?

Spoiler: It's called "TypeScript" not "InterfaceScript" 😉

Why a resolution is needed?

Typescript documentation is itself vague about which one should be used. However, vagueness is not helpful while creating a reliable software.

Most programming languages contain good parts and bad parts. I discovered that I could be a better programmer by using only the good parts and avoiding the bad parts. After all, how can you build something good out of bad parts?

Douglas Crockford

In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.

Zen of Python

type - Good, interface - Bad

1. interface is mutable

Redeclaring an interface appends to existing definition:

interface MyInterface {
  name: string
}

interface MyInterface {
  phone: number
}

let variable : MyInterface = {
  name: 'MyName',
  phone: 123456789
}
Enter fullscreen mode Exit fullscreen mode

This opens a can of worms (bugs)! In a project of significant size. Types are declared in several files, and some types are declared globally. However, if interface definition can be extended in any file, users cannot be really sure what the type is going to be at usage. Also, these bugs can be extremely challenging to locate.

On the other hand types can only be created once:

// Error: Duplicate identifier 'MyType'.
type MyType = {
  name: string
}

type MyType = {
    phone: number
}
Enter fullscreen mode Exit fullscreen mode

Mutability of interfaces is a bad feature: Value proposition of Typescript is to provide guard rails to validate types of variables in your program. Utility of Typescript is compromised if a programmer cannot be sure of what the type of interface is going to be at usage.

2. interface has unusual syntax

types naturally extend the syntax of declaring variables in JavaScript while interfaces have their own syntax which causes needless complexity.

// Variable creation rule
// Construct Name = Value; 
var x = 1;
let y = 2;
const z = 3;

// Types creation rule
// Construct Name = Value; 
type MyType = string | number;


// Interface creation rule
// Construct Name {Value}; 🤔
interface MyInterface {
  name: string
}
Enter fullscreen mode Exit fullscreen mode

3. extends on double duty

extends in Typescript has dual purpose:

  • Extending an interface: More properties can be added to an interface using extends keyword.
interface Animal {
  name: string
}

interface Bear extends Animal {
  honey: boolean
}
Enter fullscreen mode Exit fullscreen mode
  • Conditional type checking: For inputs to a Generic extends keyword is used to validate type at usage. Additionally, for conditional types extends keyword functions as a condition checker for a type being subset of another type.
const firstChar = <T extends string>(input: T) => {
  return input[0];
}

// Works!!
firstChar('abc')

// Error: Argument of type 'string[]' is not assignable to parameter of type 'string'.
firstChar(['abc'])
Enter fullscreen mode Exit fullscreen mode

The dual usage for keyword extends is a source of lots of confusion. It makes it difficult for new users to adapt advanced TypeScript features.
However, we can do better! By not using interface at all we can remove this confusion as extends will be left with only one usage.

PS: Special thanks to Iván Ovejero for writing TypeScript and Set Theory

4. interface only works with objects

Now what's up with that 🤷‍♂️. In my opinion this is the origin of problem. interface was designed to make it easier to work with objects, which resulted in creation of two almost similar features and endless conversation of "type vs interface".
Software languages, libraries and frameworks follow principles of evolution. Like in any evolutionary system many species and initiatives take place to address specific needs and fill certain gaps.
However, not all initiatives successfully manage to address real problems, and sometimes can have detrimental effects on overall structure of program. Unfortunately, unsuccessful experiments cannot be removed from a language due to backwards compatibility.

It is rarely possible for standards committees to remove imperfections from a language because doing so would cause the breakage of all of the bad programs that depend on those bad parts.

Douglas Crockford

But we as developers can separate good parts from bad parts and only use good parts to make our softwares great!

Top comments (16)

Collapse
 
captainyossarian profile image
yossarian

As for interface mutations. It is not a mutation. You can only add new properties but not remove. Apart from that, you can extend interface only in a scope of one module (read file). Once you add export to your file - you will be unable to extend this interface in another file.

interface in fact is much safer than type. I would say, that in most cases, you should consider using interfaces.

interface Animal {
  tag: 'animal',
  name: 'some animal'
}

declare var animal: Animal;

const handleRecord = (obj:Record<string, unknown>) => { }

// Change `interface` to `type` and error will be resolved
const result = handleRecord(animal) // error
Enter fullscreen mode Exit fullscreen mode

As for performance and intersections, please see typescript wiki page
They suggest to use interface with extends over type intersection

// BAD
type Foo = Bar & Baz & {
  someProp: string;
}

// GOOD
interface Foo extends Bar, Baz {
  someProp: string;
}
Enter fullscreen mode Exit fullscreen mode

You need to use type alias mostly for writing type utilities, like mapping and conditional types or declaring unions.

Please see this stackoverflow answer

Indexing was already mentioned, but there is another one thing. Interfaces are eagerly evaluated. See second part of the answer

To summarize:
Interfaces
Use interfaces when you need to declare an object type. In most cases, you should start from interface

Types
Use type aliases for declaring conditional types and utility types

Collapse
 
udayanmaurya profile image
Udayan Maurya

Thanks for your input you do bring about two valid points that interface not being appendable across files and eager evaluation!

However, for the example:

interface Animal {
  tag: 'animal',
  name: 'some animal'
}

declare var animal: Animal;

const handleRecord = (obj:Record<string, unknown>) => { }

// Change `interface` to `type` and error will be resolved
const result = handleRecord(animal) // error
Enter fullscreen mode Exit fullscreen mode

Why do you think it should error?

For the performance issue. I've shared the benchmark I've found here.
Full project level compilation mostly happens in CI and does not affect developer productivity. I'd estimate in most extreme cases intersects can take around 5min more in CI, which is generally not a bottleneck.
However, inconsistent code and tech debt caused by confusion and ambiguity causes more harm to codebase (bugs created) and developer productivity (resolve bugs/add new features).
I'd almost always advocate for coding patterns which reduce ambiguity and can be scaled in large teams of people with varied level of skill-sets.

You should checkout youtube.com/watch?v=NPB34lDZj3E for context.

Collapse
 
captainyossarian profile image
yossarian

Why do you think it should error?

Because Record is indexed and interface Animal is not. I am not sure why are you talking about inconsistent code, tech debt and The Post JavaScript Apocalypse - Douglas Crockford video. I don't think it is relevant. There is an official typescript wiki. But it is up to you which patterns needs to be used in your project. NO hard feelings

Collapse
 
alaindet profile image
Alain D'Ettorre

I've been wandering between type and interface for years, I've settled for interface for years but now I favor type instead since it's much more flexible and can do the same as well. Just a few examples that can only be achieved with type

type ButtonColor = 'primary' | 'secondary' | 'outline';
type TodoItem = { id: string; title: string; done: boolean; };
type CreateTodoItemDto = Omit<TodoItem, 'id'>;
type Person = { name: string; age: number };
type Programmer = Person & { favoriteLanguage: string; };
Enter fullscreen mode Exit fullscreen mode
Collapse
 
retyui profile image
Davyd NRB

one point that was missed Index signature for type 'number|string' is missing in interface

Alos, all arguments about extend nothing until: type MyType = Foo & Bar slower than interface MyType extends Bar, Baz {} (read officatial performance docs: github.com/microsoft/TypeScript/wi...)

Collapse
 
udayanmaurya profile image
Udayan Maurya • Edited

Thanks for sharing the index signature issue. I was not aware of it!

I have ran performance benchmark for 10k intersect vs 10k interface extend. Results

  • Interface extend: 1 min 26 sec 629 ms
  • Type intersect: 2 min 33 sec 117 ms

10k intersections can be possible in a fairly large project. Types take around a minute more to compile. This cost is generally incurred in CI pipelines and not during development. For development cases only concerned types per file are resolved, which should remain instantaneous in most cases.
A few minutes of additional compile time in CI, in more heavy weight cases, is a good trade off to avoid all the gotchas associated with interface.

Collapse
 
miketalbot profile image
Mike Talbot ⭐ • Edited

Isn't it the case that an object can have many Interfaces but only one Type? The point of interfaces is normally to be able to treat many different object types with the same code for specific functions that use the interface.

Collapse
 
brense profile image
Rense Bakker

This is perfectly fine:

const myObj: SomeType & AnotherType & MoreType = {
  // Use all properties from 3 types
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
miketalbot profile image
Mike Talbot ⭐

Great thanks for that, in my few experiences I've gone the C# style route and used interfaces where there are multiple and class types otherwise.

Collapse
 
sandeepbansal_2 profile image
Sandeep Bansal - 2/100

Great post, thank you for sharing your thoughts and insights

You've raised some valid points about the potential issues with using interfaces, particularly in terms of mutability, syntax, and the confusion surrounding the use of the "extends" keyword.

My question for you would be: In your opinion, are there any situations or use cases where interfaces would still be the preferred choice over types? Are there any benefits to using interfaces that you think are worth considering, even with the potential issues you've outlined?

Collapse
 
udayanmaurya profile image
Udayan Maurya

Short answer: No.
Long answer: Purpose of the post was to resolve ambiguity surrounding type vs interface debate, following Douglas Crokford's Good parts philosophy.

Good parts philosophy: Software languages/frameworks will continue to have bad parts because of failed experiments, and failed experiments cannot be removed to support all the code which has been written using those parts. Therefore, onus of separating good parts from bad parts falls on to developers.
Here is a good video for more: youtube.com/watch?v=NPB34lDZj3E.

Collapse
 
captainyossarian profile image
yossarian
Collapse
 
the_yamiteru profile image
Yamiteru

Nice comparison but I personally wouldn't use words such as good or bad.

If you use such words you really miss the point. Both types and interfaces have their use-cases and for those use-cases they're always "good" (the obvious way to do it).

So what I'd rather see is a list of use-cases with examples of types and interfaces. No comparing which is better or worse.

Collapse
 
brense profile image
Rense Bakker

I think interfaces were meant to be used with class based typescript (oop). In that context its a better known concept. For functional programming I agree type is better.

Collapse
 
miketung profile image
Mike Tung

Trying to impose python ways on a typescript/javascript ecosystem is not practical. It's like trying to impose communism on the US.

Collapse
 
mystyle2006 profile image
Inho

Thanks :)