Some time ago, I came to the conclusion that the TypeScript type keyword should almost always be favoured over the interface keyword. I arrived at this conclusion after considering the use cases and implications. Of course, there is a large overlap, but I personally believe that interfaces (in TypeScript) are a more restrictive and less useful construct than types.
Overlapping functionality
When I first used TypeScript, I was surprised that two language features could be so similar as to appear almost indistinguishable.
Both allow for declaring a contract or shape of a type1
Both can be members of a union type
Both can be extended to create derived types
Both can be used for OO inheritance with classes and the implements keyword
Types are more widely applicable
The type construct is more general purpose than interfaces. Consider the following code block.
Not only was a contract or shape of a user-defined, but a union type was also defined. A clear advantage is revealed; types can express multiple constructs and are more versatile than interfaces.
Union types are also explicitly part of the syntax, such as string | number. That is, types are a more natural and everyday occurrence in TypeScript. For example, consider the following code block.
What has been achieved here compared to the first code block, other than extra characters and using two keywords and language features over one? Not much - other than confusion when revisiting the codebase and raising questions as to why both were used.
Using types is more consistent
The previous section demonstrates that types are less restrictive and surpass the use cases of interfaces. Continuing to use both keywords is simply inconsistent for no good reason. Making the choice between them is not difficult. The stricter but more versatile keyword or the looser and less useful keyword?
Type wins.
Interfaces can be merged (that’s rarely good)
Interfaces support declaration merging. This is definetly a “feature” to be cautious of. It has two good use cases - creating or extending the typings of third party libraries and declaring environment variables. It’s the relative complement of the Venn diagram (the tiny bit of orange that does not intersect the blue).
Outside of those two use cases, I do not see any value in the following code block. It’s simply confusing, especially if multiple interfaces with the same name are created throughout the code base.
Interfaces lean more towards OO thinking
Interfaces are, by their nature, strongly tied to classes. I would even suggest that interfaces were added to TypeScript specifically to support the class-based OO paradigm.
To be clear, I’m not saying OO is bad (though I often favour functional concepts), but let’s be realistic - the TypeScript (and JavaScript) ecosystem leans strongly towards approximately two paradigms: non-class based object orientation and functional.
I can only think of two examples where I used classes or OO with TypeScript recently. This strongly contrasts with a typical project using UI libraries like React, where simple functions + data (often in the form of hooks) and declarative patterns and concepts are commonplace2.
There are some common myths used to “sell” TypeScript to developers. I believe some of these myths are harmful and perpetuate this “let’s apply OO everywhere without considering the implications of the ecosystem”. One of these is that because TypeScript has classes, it is an OO language - it’s a multi-paradigm language. Aside from the fact that JavaScript also has classes, this myth is popular amongst developers who have only experienced OO.
This is a topic I’d like to expand on more in another article, so I’ll finish this section with an insightful talk from Douglas Crockford.
There are a few we know are gonna be bad. The worst is class. Class was the most requested new feature in JavaScript and all of the requests came from Java programmers who have to program in JavaScript and don’t want to have to learn how to do that.
Types can still be used with classes
Again demonstrating the versatility of types, I created a customer processor in a typical OO style. There are two types available for implementation, and both are examples of higher-order functions (or in OO terminology: dependency injection). No interfaces here, yet still featuring composition.
I’ve explained my preference and why I believe type should be the default choice in your projects. It’s up to you which you use, but I hope you can see the benefits of types and why they are a better choice in most cases.
The inevitable angry comments can be directed to /dev/null.
Footnotes
The term “types” in TypeScript can refer to any object, be it a type alias or an interface. However, in the TypeScript ecosystem, the word “type” is typically usually used to specifically refer to the type keyword. This distinction is generally understood, even amongst pedants who are particular about precise language usage. ↩
Angular being the opposite and the framework of choice for the “I don’t want to learn JavaScript/TypeScript” crowd. ↩