10/10

Article thumbnail

Types vs interfaces in TypeScript fully explained

7m
research
performance
builds
compilation

Intro

Explore TypeScript's nuances - "type" vs. "interface." Uncover use cases and their impact on compile and build times for a comprehensive understanding.

Why types vs interfaces?

As a TypeScript developer, it's good to use all the possibilities and features that this technology gives us. However, there are areas, weird parts, the same as in the JavaScript that make our learning process chaotic. The best example is good and well-known: "Types vs interfaces" subject.

To be able to use TypeScript features fully and with pride, today we'll explore these concepts:

  1. differences in syntax, use cases and mindset,
  2. mapped types don't work with interfaces,
  3. unions don't work with interfaces,
  4. differences in declarations merging,
  5. types can be merged with interfaces and vice-versa,
  6. types and interfaces with classes,
  7. cosmetic differences,
  8. impact on build/compile time.

At the end, we'll know the answer to the question: "Should I use types or interfaces?"

Differences in syntax, use cases and mindset

In TypeScript we can create type definitions (contracts for our code) in following ways:

Loading

Loading

However, the interface keyword is reserved only for objects creation, but with type we can create definitions for everything.

Loading

In the example above, we created definitions for array and function - they are objects too. This is how JavaScript works and it's obvious that TypeScript follows the same rules. Now let's check the types syntax.

Loading

Mapped types don't work with interfaces

Mapped types allows you to create new types by transforming the properties of an existing type based on a mapping function. They are often used to perform operations on the keys and/or values of an object type to create a new type.

Loading

Loading

Unions don't work with interfaces

If you want to create a declaration of an object that is A or B with interfaces, it's not possible.

Loading

Differences in declarations merging

Interfaces implements something that is called automatic declaration merging. How it works? You need to define more than one interface with the same name and their definitions will be merged.

Loading

The same attempt done in type context doesn't work - they must have unique names per scope.

Loading

So, we know that interfaces have additional feature - implict declarations merging. In another way, declarations with type keyword must have unique names per scope.

However, we can merge declarations in explicit way and the syntax in both is different.

Loading

Loading

The result will be the same object. The only difference between them in this context is the syntax. For interfaces we need to use extends keyword but for type we need to use merge operator - "&".

But why did someone added auto-merge for interfaces? Imagine a library with badly written type definitions and you want to consume this library API. There is an additional function added, but someone forgot to add a type definition for this function or it's done in the wrong way. Thanks to this feature you are able to override it easily.

Loading

The other use case will be just removing the need to write the extends keyword all the time. It's the next, fancy feature. We have a lot of them in JS. Instead of typing we just need to know how something works.

The real-world example will be a situation in which library doesn't have any type definitions. In the example below, we're adding full typing for gatsby-plugin-dark-mode library.

Loading

Types can be merged with interfaces and vice-versa

Type and interface declarations are compatible in the merging aspect.

Loading

Types and interfaces with classes

Both may be used in the context of classes in the same way - to implement contracts.

Loading

Cosmetic differences

Look at the following code sample to understand the amount of code that we need to write for both.

Loading

Type-based declarations are just easier to write :P, that's why a lot of current eslint rules detect such cases as above and warn you to rewrite it to type syntax.

The difference in developer experience

Let's say you defined your interfaces and types in a separate file, and you want to use them in another place. The interface will hide the shape of the object when you hover over it, and the type will not. Look how it looks like in VS code.

Loading

Behavior in VS code

You can read the shape of the interface. You just need to click on the interface and you will be moved to its definition. In VS code it ctrl + left mouse button.

Impact on build/compile time

Under this repo you have official recommendations on when to use types and when to use interfaces. The authors of this document provide arguments connected with performance.

By saying performance I mean:

  • how long build process take,
  • how fast your real-time plugins work in IDE,
  • how long the compilation process take.

The TypeScript is not providing any additional code when complied to JavaScript.

In old versions of TS there was a small impact with enum, but in modern TS you can avoid this.

I will verify this impact by changing interfaces to types in my big repository in which I have almost 500 interfaces. Later, I'll measure the build/compilation and developer experience in real time (I'm working with VS code).

So I'll execute following commands to compile and build:

tsc --project tsconfig.base.json --diagnostics - compilation.

npx nx run-many --target=build --all - build.

The --diagnostics flag will force compiler to prompt results.

Both commands will be executed on 2 separate branches. First with interfaces and second with types.

Under this repository you have code before changes (with interfaces).

Under this repository you have code after changes (with types).

This is how I made a changes:

Loading

The change from interfaces to types process

The result is really interesting... Looks like it's really improving the build time and compile time.

Loading

Build time with interfaces

Loading

Build time with types

Loading

Compile time with interfaces

Loading

Compile time with types

I repeated these tests many times. In every attempt I cleaned the build folders and cache. In the context of numbers, there was always a difference, higher or lower, but the conclusion is: the version with interfaces had always faster build and compilation time.

In addition, I tried real-time development on these branches. There were some situations in which when I accessed a file the first time I got a small lag or a longer response from the local TypeScript check server. So, looks like there is an impact too.

If you are curious how it looks on your computer just open above mentioned branches and run tests 🔝.

Should you use types or interfaces?

We spotted a lot of differences between declaring definitions with types and interfaces. Some additional features are available in both of them, but the most important aspect is still the question from the beginning: "Should I use type or interfaces?"

I think the answer is really simple to this question. If TypeScript creators recommend using interfaces for defining objects, we should trust them. In addition, we performed some checks so we know how big the impact is on large projects. It may be several seconds on each build!

So, the real answer is: "Use interfaces first for defining objects until you need a feature from type-based definitions" - of course if you care about development experience and compile/build performance.

I create content regularly!

I hope you found my post interesting. If so, maybe you'll take a peek at my LinkedIn, where I publish posts daily.

Comments

Add your honest opinion about this article and help us improve the content.

10/10

created: 21-09-2023
updated: 06-12-2023