Code condition for checking if is time to learn

TypeScript: Interfaces vs Types

A simplified approach to type declaration

Author's avatar.

David Linhares

Apr 21, 2025 ・ 12 min read

When working in TypeScript applications, one is often faced with the dilemma of, “How should I declare this type?”

This question is often overlooked, and depending on the developer, the decision might be simple: using what I am accustomed to.

Some developers come from a backend software development background, and having worked with strongly typed languages like Java for quite some time can make the use of “interface” a no-brainer. Others may have only worked in JavaScript/TypeScript applications and see the benefit of concise type declarations, as well as the ease of creating types from other types.

The real question is: how should we approach this problem?

  1. TypeScript performance
  2. Is performance really the key?
  3. What is a common use case for this performance boost?
  4. Takeaway
  5. Final thoughts

TypeScript performance

This very question is somewhat answered in the TypeScript Performance wiki, and I quote:

“Interfaces create a single flat object type that detects property conflicts, which are usually important to resolve! Intersections on the other hand just recursively merge properties, and in some cases produce never”.

What does that even mean?

Well, in simple terms, when you declare an interface, you’re setting up your project so that, when you need to declare other object types, you can extend them instead of intersecting them. Let’s take the example of the following object type directly from the wiki above:

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

By intersecting on the type, we are triggering a mechanism from the TypeScript compiler that will recursively merge the objects in order to suffice the type Foo, where by using the interface declaration for the same purpose, we would be creating a single flat object, which in terms of performance allows for a much faster build:

interface Foo extends Bar, Baz {
  someProp: string;
}

Is performance really the key?

The question then becomes, “How much performance do you need from TypeScript?” At the time of writing this article, TypeScript is about to undergo a major performance improvement, which promises to deliver a 10x faster TypeScript. This is all due to a port of the TypeScript compiler and tools to native code (using golang). All of this is to say that performance is always top of mind for software developers, especially those working on the building blocks of today’s web.

When assessing questions related to performance aspects of software development, I like to think about how they usually translate to the hardware requirements for accomplishing the task. In this case, by using a more performant approach to defining types in our application, we are writing TypeScript code that is simpler to compile. This leads to fewer resources being required to accomplish the task, which in most cases would result in less runtime on CI/CD pipelines, thereby reducing software development costs.

Costs are a major component of the regular software development lifecycle and developer experience. The benefits of more performant code generally translate to developers having a better toolset to accomplish their tasks faster, and to users experiencing improved app performance.

I say “generally” because the performance improvement, although welcome, may not produce the expected result in productivity or user experience. This could be due to the performance increase being negligible, or because it is not relevant compared to other business-related requirements (such as prioritizing the development of features that attract users over improving the current user experience).

What is a common use case for this performance boost?

Well, glad you asked. Let’s consider, for instance, a very common use case these days: a React application with tons of components and pages.

Large codebases are good candidates for performance improvements because, in most cases, as a codebase grows into a large software product, tools, practices, paradigms, and other aspects of the software development lifecycle have been updated and improved. By the time a codebase is considered large, it may have undergone some good old-fashioned refactoring, dependency updates, tooling updates, and so on.

Now imagine a large codebase that includes hundreds of React components. Each component has its own type declarations, as well as a few sub-components. There may also be more type declarations and shared common components that are extended by other, more complex components, and so on. As pointed out by Matt Pocock in This Pattern Will Wreck Your React App’s TS Performance, just by making a simple syntax change—turning props from types (with intersection) to interfaces that extend React’s base attributes for common HTML elements—the TSC compile time was significantly reduced in the use case referenced in his article.

Takeaway

Since interfaces are easier on TSC’s performance, the approach of defining interfaces until you need to use a type or a feature from type declarations makes a lot of sense to me. It is a simple rule of thumb and can be very easy to implement on a daily basis. AAs a developer, you are not losing anything in terms of programming language standards and best practices, and at the same time, you are setting up your project with performance in mind and potentially implementing cost-reducing practices that could positively impact your team.

Final thoughts

Although declaring types is a common task in the daily routine of a TypeScript developer, attention to detail can be helpful when considering the performance impacts of such a common practice.

See ya! 👋