React|Essential of Language and Web

Type vs Interface: Scalability, Performance, and Our Team's Decision Criteria

5
Type vs Interface: Scalability, Performance, and Our Team's Decision Criteria

When starting a TypeScript project, there is one topic that always divides opinion among team members right off the bat: What should we use to define object types?

// What's the difference?
type User = {
  name: string;
}

interface User {
  name: string;
}

Functionally, they behave identically in over 90% of situations. So, does it mean we can just use whatever we want? Or is there a hidden performance secret that "Chris" (the developer) isn't aware of?

Today, we will summarize the decisive differences between type and interface and establish the correct criteria for choosing between them based on the situation.

1. Declaration Merging

The biggest difference lies in "Extensibility."

Let's assume Chris is building an open-source library. If a user needs to overwrite or add to the library's settings, an interface is essential.

Interface: An Open Blueprint

If you declare an interface with the same name multiple times, TypeScript automatically merges them into one. This is called Declaration Merging.

// global-window.d.ts
// 1. Existing Window interface
interface Window {
  title: string;
}

// 2. Property I added (No error, automatically merged)
interface Window {
  googleAnalyticsId: string;
}

// ✅ Result: Window has both 'title' and 'googleAnalyticsId'
const w: Window = window;
w.googleAnalyticsId = 'UA-XXXX';

Type: A Closed Alias

On the other hand, a type cannot be redeclared. It strictly warns you that "this name is already taken."

// config.ts
type UserConfig = {
  theme: 'dark' | 'light';
}

// ❌ Error: Duplicate identifier 'UserConfig'.
type UserConfig = {
  apiKey: string;
}

Because of this characteristic, you must use interface for library type definitions (e.g., @types/react) or when extending global objects.

2. Differences in Expressiveness (Union & Tuple)

type possesses powerful features that interface cannot handle. These are Union Types and defining Primitive values.

// api-types.ts

// 1. Union Type: Impossible to express with interface
type Status = 'pending' | 'success' | 'error';

// 2. Tuple Type
type Coordinates = [number, number];

// 3. Primitive Type Alias
type UUID = string;

interface is specialized strictly for defining the structure of an Object. Conversely, type acts like an all-purpose label maker that can attach a name to any form of data.

3. Performance Issues: Is There Really a Difference?

We often hear the claim, "Interfaces perform better than types." Is this true?

In the past, interface was definitely faster in terms of compilation speed. However, in modern TypeScript versions, the gap has become negligible. That said, interface still holds some advantages due to structural differences:

  • Caching: Interfaces are easier to cache simply by their name. In contrast, Intersection Types using type (A & B) often require the compiler to recursively merge and check properties every time.
  • Error Messages: When an error occurs, an interface displays the interface name as is. However, complex type (Aliases) definitions tend to "unroll" and display the full object literal ({ a: string; b: number }), which can reduce readability.
  • Therefore, if you are working on a large-scale project handling very complex object types, using interface offers a slight advantage in compilation performance and error debugging.

    4. Our Team's Selection Criteria (Best Practice)

    So, how should we decide in practice? Combining official documentation and community conventions, I propose the following criteria:

    Strategy: Interface First, Type for Specifics

  • Use interface as the default for Object definitions.
  • Use type for things interface cannot express.
  • Use interface when writing libraries or public modules.
  • // ✅ Good Example: user.ts
    
    // Use interface for object structure
    interface User {
      id: number;
      name: string;
    }
    
    // Use type for state values, etc.
    type UserRole = 'admin' | 'member' | 'guest';
    
    // Function types are a matter of preference, but type is slightly more concise
    type UpdateUser = (user: User) => void;

    Key Takeaways

  • Extensibility: interface allows declaration merging, making it open to extension; type is closed.
  • Expressiveness: type can define a wider variety of types, including Unions (|), Intersections (&), and primitives.
  • Performance: When defining object types, interface has a slight advantage in compilation performance and error message readability.
  • Ultimately, the most rational strategy is to approach it as: "Use interface for the skeleton (objects), and use type for complex combinations."

    🔗 References

  • Interfaces vs Types in TypeScript
  • TypeScript Performance Wiki

  • Next Post Teaser:

    Now that we understand the operating principles of JavaScript, it's time to add types to build robust code.

    Continuing in: "Asynchronous JavaScript and the Event Loop: How Do Promises Work?"