DEV Community

Andrew S.
Andrew S.

Posted on • Edited on

Types vs Interfaces in TypeScript

Interface

Interfaces are a basic TypeScript concept and are widely used to define the shape of objects.

They are primarily used to declare contracts, providing a way to define the shape of an object that a class or other objects must follow.

// Interface usage
interface Person {
  name: string;
  age: number;
}

const person: Person = {
  name: "John",
  age: 30,
};
Enter fullscreen mode Exit fullscreen mode

Interfaces can be extended from each other (extends), allowing you to create more specialised interfaces based on existing ones.

They support Declaration Merging, which means you can declare an interface in multiple places, and TypeScript will merge them into a single definition.

Type Alias (Type)

Type Aliases allow you to create a new name for a specific type. They are used to give a descriptive name to complex types or to create unions, intersections, and other complex types.

Type Aliases can be used with objects, but they are more versatile and can be used with other types, such as unions, intersections, typle, and primitive types.

Unlike interfaces, types cannot be extended.

// Type Alias usage
type Person = {
  name: string;
  age: number;
};

const person: Person = {
  name: "John",
  age: 30,
};


// Union of Type
type Status = "pending" | "in_progress" | "completed";
Enter fullscreen mode Exit fullscreen mode

In general, if you need to create a contract for an object shape or if you plan to extend or combine definitions, interfaces are usually the better choice. On the other hand, if you need to create a new name for a particular type or work with unions and intersections, type aliases provide more flexibility.

Differences between Interface and Type

1. Usage

If we consider the option of using these entities for objects, in some cases they are interchangeable. Let's consider an example:

interface Interface {
  name: string;
}

type Type = {
  name: string;
}
Enter fullscreen mode Exit fullscreen mode

In this case, it doesn't make much difference what to use. Both of these options are suitable.

However, if we are talking about other entities rather than objects, then things are not so clear. For such cases, we should use Type. Here are some examples:

type Function = (prop: string) => string;

type Line = string;

type Permission = "admin" | "user";
Enter fullscreen mode Exit fullscreen mode

Use interfaces if you need to define the structure of objects and expect to use Class Implements or need to Combine Declarations.

Use types if you need more flexibility with unions or when defining functions and other TypeScript-specific utilities.

2. Extends

There is such a functionality for interfaces as extensions. Let's imagine a simple Animal interface that represents an animal:

interface Animal {
  name: string;
}
Enter fullscreen mode Exit fullscreen mode

The object that implements this interface will have one property - name. In fact, if we want, we can call any animal by any name, so this is quite justified.

Now let's look at the extension of this interface.

interface Dog extends Animal {
  bark: () => void;
}
Enter fullscreen mode Exit fullscreen mode

In this case, we create an interface Dog, which creates a more specific type of animal - dogs. And in it we implement the bark method - barking. Since not all animals bark, but only dogs, this method is a specific property.

And let's use this interface to create an object:

const dog: Dog = {
  name: 'Bean',
  bark: () => console.log('Bark')
}
Enter fullscreen mode Exit fullscreen mode

As you can see from this example, interface has the ability to create new interfaces based on previous ones.

At the same time, types are also extensible, but the syntax is slightly different:

type BaseType = {
  prop1: string;
};

type ExtendedType = BaseType & {
  prop2: number;
}; 
Enter fullscreen mode Exit fullscreen mode

3. Implementation (implements) of the Interface

Interfaces can be implemented by classes, which means that a class can explicitly state that it implements a particular interface and must provide an implementation for all members defined in the interface. Types, on the other hand, cannot be directly implemented by classes.

Since interface is an OOP entity, we can use it as intended. Here is an example of an implementation:

interface Human {
  name: string;
}

class Woman implements Human {
  sex: string = 'female';
  name: string;

  constructor(name: string) {
    this.name = name;
  }
}
Enter fullscreen mode Exit fullscreen mode

Here we have created interface Human, which means a person. And we create the class Woman. In this context, it is clear that Human should not be a class, because people are either men or women. At the same time, interface does not limit us to multiple inheritance. We could create several more interfaces for class Woman.

In the case of type, this is not possible because they are not directly related to OOP.

4. Declaration Merging

Interfaces in TypeScript have a unique property called Declaration Merging that allows you to extend an existing interface by declaring it again. This means that you can split an interface definition into several parts. On the other hand, types cannot be merged. If you try to define a type with the same name more than once, an error will occur. Here is an example of combining interface definitions:

interface MyInterface 
  prop1: boolean;
}

interface MyInterface {
  prop2: string;
} 
Enter fullscreen mode Exit fullscreen mode

5. Index signatures in types and interfaces

The other difference between interfaces and types is very subtle.

Type aliases have an implicit index signature, while interfaces do not. This means that they can be assigned to types that have an index signature, but not to interfaces. This can lead to errors such as:

interface Attributes {
  x: number;
  y: number;
}

const attributes: Attributes = {
  x: 1,
  y: 2,
};

type RecordType = Record<string, number>;

const oi: RecordType = attributes;
// Type 'Attributes' is not assignable to type 'RecordType'.
// Index signature for type 'string' is missing in type 'Attributes'.
Enter fullscreen mode Exit fullscreen mode

The reason for this error is that the interface may be extended later. A property may be added to it that does not match a string key or a number value.

You can fix this by adding an explicit index signature to your interface:

interface Attributes {
  x: number;
  y: number;
  [index: string]: unknown; // New!
}
Enter fullscreen mode Exit fullscreen mode

Or simply change it to type:

type Attributes = {
  x: number;
  y: number;
};

const attributes: Attributes = {
  x: 1,
  y: 2,
};

type RecordType = Record<string, number>;

const oi: RecordType = attributes;
Enter fullscreen mode Exit fullscreen mode

6. Readability and preferences

  • Interfaces are often preferred for defining the shape of objects because of their explicit contractual nature. They are self-documenting and can improve code readability.
  • Type aliases are commonly used when defining complex types or working with unions and intersections. They can provide more concise and descriptive names for complex types.

Conclusion

Use interfaces when:

  • You want to define the structure of an object or function.
  • You need to Merge Declarations.
  • You work with a team that prefers interfaces for consistency.

Use type aliases when:

  • You need to create an alias type for union, intersection, or other complex types.
  • You're working with a library or third-party code that makes extensive use of types.
  • You prefer a more general approach and don't need to Merge Declarations.

In recent versions of TypeScript, the distinction between interfaces and type aliases has become less pronounced, and they are often used interchangeably. In practice, many developers use interfaces for most object-related scripting and reserve types for more complex type manipulation. However, TypeScript is flexible, and you can achieve similar results with either approach.

Remember that the most important factor is the consistency of your code base. So if your project already has a convention, it's generally best to stick with it. If you are starting a new project, discuss this decision with your team to create a consistent approach.

Support ❤️

It took a lot of time and effort to create this material. If you found this article useful or interesting, please support my work with a small donation. It will help me to continue sharing my knowledge and ideas.

Make a contribution or Subscription to the author's content: Buy me a Coffee, Patreon, PayPal.

Top comments (0)