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
}
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
}
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
}
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;
}
Meanwhile, with type
, you can do this:
type user = {
name: string
}
type newUser = user & {
name: number
}
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
}
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;
}
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;
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 {
}
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
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)
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:
Actually, with types you can do pretty much the same using an Intersection Type with
&
.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 :)
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).
Now let's add some other aspect on the difference between both. Compiler Performance ;)
github.com/microsoft/TypeScript/wi...
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.
regarding unions. you can do the following
type foo = ifaceA | ifaceB
yeah that is true to be fair
Excelente post, I was looking for this info
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 theinterface user
to betype user
instead:Link to TS Playground
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
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!
Thanks for this article.
There is also a difference on imports.
It isn't possible to
import type {} from '...'
with an Interface.interface is also possible.
cool!!!!!!