DEV Community

Cover image for The Difference between TypeScript Interfaces and Types
Johnny Simpson
Johnny Simpson

Posted on • Edited on • Originally published at fjolt.com

The Difference between TypeScript Interfaces and Types

In TypeScript, you might have noticed you can declare custom types in two different ways. One is with the interface keyword, and the other is with the type keyword. As such, you may find yourself wondering why there are two ways to do one thing - and you're not alone. I already covered in my guide to declaring custom types in TypeScript, how you can use interfaces and types - but let's look a little bit more into how they differ.

1. Interfaces extend syntax is different to types

If we define a type in TypeScript, it is not extendable after the fact. For example, consider this custom type I just made up:

type user = {
    name: string,
    age: number
}
Enter fullscreen mode Exit fullscreen mode

If, after its been defined, I suddenly realise I want to add an address too, I can do this using the following syntax:

type userWithAddress = user & {
    address: string
}
Enter fullscreen mode Exit fullscreen mode

With interfaces, we can do the same, but the syntax remains slightly different:

interface user {
    name: string;
    age: number;
}
interface userWithAddress extends user {
    address: string
}
Enter fullscreen mode Exit fullscreen mode

Now userWithAddress contains all the properties of user, plus one additional property - that being address.

The only difference between these two ways of extending types is how they handle conflicts. For example, if you extend an interface and mention a property that has already been defined, an error will be thrown. For example, this will not work:

interface user {
    name: string;
}
interface newUser extends user {
    name: number;
}
Enter fullscreen mode Exit fullscreen mode

Meanwhile, with type, you can do this:

type user = {
    name: string
}

type newUser = user & {
    name: number
}
Enter fullscreen mode Exit fullscreen mode

While this will not throw an error, it may result in some unexpected results - so you should avoid it where necessary. For example, above, the name property is reduced to type never - since a type can never be both string and number at the same time. :)

2. Interfaces can be merged - types cannot

Along the same lines, types cannot be merged, while interfaces can if you declare them multiple times. For example, if we have a type, we can't do something like this:

type cat = {
    name: string
}
type cat = {
    color: string
}
Enter fullscreen mode Exit fullscreen mode

In fact, the above code will throw an error. Meanwhile, with interface, we can do that - and it'll merge both declarations. So the example below will create a type called cat with both name and color properties:

interface cat {
    name: string;
}
interface cat {
    color: string;
}
Enter fullscreen mode Exit fullscreen mode

3. Interfaces cannot extend a primative

While we can create a type which can be an alias for a primitive type like string, interface cannot do this. For example, if you want to create a type called myName, which is always of type string, we can do it like this:

type myName = string;
Enter fullscreen mode Exit fullscreen mode

Here, myName becomes an alias for string - so we can write myName instead of string, essentially, anywhere. Meanwhile, interface does not have this ability. The following cannot and will not work:

interface myName extends string {

}
Enter fullscreen mode Exit fullscreen mode

4. Types can create unions, while interfaces cannot

We can create union types with the type keyword, but we can't do that with an interface. For example, here, userId can be a string or number:

type userId = string | number
Enter fullscreen mode Exit fullscreen mode

Meanwhile, the above cannot be achieved with an interface, since interface defines the shape or type of an object.

Conclusion

As you can see, the main differences between type and interface kind of depend on the circumstances you use them in. Almost all the features of interface are available in type, which means you might find yourself more frequently going to type. Typically, though, it tends to be based on preference or what works best in your codebase.

In any case, rest assured that it isn't as confusing as you might first think - with both interface and type effectively being two ways to do the same thing!

Top comments (14)

Collapse
 
balastrong profile image
Leonardo Montini • Edited

Hey, thanks for the article!
I've got a thing to mention on point 1 which reads "Interfaces are extendable - types are not".

You say:

If, after its been defined, I suddenly realise I want to add an address too, I will need to change the original type declaration. With interfaces, we can declare something new, and extend user

Actually, with types you can do pretty much the same using an Intersection Type with &.

type user = {
    name: string,
    age: number
}

type userWithAddress = user & {
    address: string
}
Enter fullscreen mode Exit fullscreen mode

The result is pretty much the same as with interfaces. The only difference is (as also explained in the doc) on conflicts, but as long as all fields are different, you can extend types with other types :)

Collapse
 
smpnjn profile image
Johnny Simpson • Edited

hey, that's a good point that I forgot about. Let me update the article. I've also updated to include the info on conflicts which is quite interesting (and something I didn't know about before, actually).

Collapse
 
sschneiderihrepvs profile image
sschneider-ihre-pvs

Now let's add some other aspect on the difference between both. Compiler Performance ;)

github.com/microsoft/TypeScript/wi...

Collapse
 
smpnjn profile image
Johnny Simpson

Ah very interesting.. makes me wonder how much time is lost to compiling typescript projects per year per developer from badly written TS code haha.

Collapse
 
sschneiderihrepvs profile image
sschneider-ihre-pvs

Image description

Collapse
 
adonis profile image
Adis Durakovic

regarding unions. you can do the following

type foo = ifaceA | ifaceB

Collapse
 
smpnjn profile image
Johnny Simpson

yeah that is true to be fair

Collapse
 
jafb321 profile image
Jose Antonio Felix

Excelente post, I was looking for this info

Collapse
 
epidemian profile image
Demian Ferreiro • Edited

Point 5, "Classes can implement interfaces, but not types", is not correct. IDK if it was the case on a previous version of TypeScript, but at least in the current one (4.8) the example with the createUser class works the same if you change the interface user to be type user instead:

type user = {
    name: string;
    age: number;
}

class createUser implements user {
  name = "John";
  age = 143;
}
Enter fullscreen mode Exit fullscreen mode

Link to TS Playground

Collapse
 
smpnjn profile image
Johnny Simpson • Edited

thanks - this must be new since older versions don't allow for this - and I assumed it was still the case. I'll update the article

Collapse
 
webjose profile image
José Pablo Ramírez Vargas • Edited

I liked this. To the point and clear. May I ask for a favor? I am the author of wj-config. It is my first-ever TypeScript project and while largely successful based on my experimentation with the end result, I bet there are a lot of things in its source code that can be done better.

Favor is: Could you browse the source code and point out the errors you see? You can blog about those, and feel free to call out the errors you see in the repo. Of course, I'll probably correct them in due time, so don't expect them to be there forever. Cheers!

Collapse
 
bgrand_ch profile image
Benjamin Grand • Edited

Thanks for this article.

There is also a difference on imports.

It isn't possible to import type {} from '...' with an Interface.

Collapse
 
woow_wu7 profile image
7

interface is also possible.

Collapse
 
mhcrocky profile image
mhcrocky

cool!!!!!!