TypeScript Interface vs Type: Differences and Best Use Cases

TypeScript Interface vs Type: Differences and Best Use Cases

Developers’ preference for a statically typed language increased the usage of TypeScript in modern web development, especially as a replacement for JavaScript. A major advantage of using TypeScript over JavaScript is that TypeScript uses static typing, which performs type-checking at compile time.

There are two ways of defining types in TypeScript: types and interfaces. To best use TypeScript, developers must know which interfaces and types to use and when. This blog post will discuss TypeScript interface vs type, how they can be used to define types, and how to use each.

Types in TypeScript

If you have prior experience coding in a statically typed language, then the concept of types will be familiar. Types in TypeScript are used to define the properties of data we’re working with, particularly the type. The fundamental types include string, boolean, number, and array.

With type aliases, you can create a custom name for existing types. Type aliases are created using the type keyword, which allows you to change the name of a type without explicitly creating a new type. Let’s see how this works:

type Company = string; //basic type alias
/ object type alias /
type Profile = {
  id: number;
  name: string;
  Age: number;
  email: string;
}
// union type alias
type ID = string | number;

With the type keyword, I named the alias company, which is of the string type. I also created object and union type aliases and specified their data types.

Interfaces in TypeScript

Interfaces are primarily used to describe the shape of data, which includes its properties and methods. Interfaces define contracts that an object or class must adhere to. They play a crucial role in maintaining a consistent type structure across your codebase. Here’s an example where we define the structure of an object:

interface Company {
  name: string;
  description: string;
  isBest: boolean;
  rank: number;
}

let companyObject: Company = {
  name: "Pieces for Developers";
  description: "an on-device AI coding assistant and snippet manager";
  isBest: true;
  rank: 1;
};

Here, I created an interface named Company and then defined the data type to expect for each property. First, the name and description are of the string type, the isBest property is of Boolean type and the rank property is a number type. What we have done here is create the shape of the data the companyObject must adhere to.

As we defined using the interface keyword, the name property has the value “Pieces for Developers”, which is a string type. Next, we include the value of the description property which is “an on-device AI coding assistant and snippet manager”, also of string type. For the isBest property, we set the value to true, which confirms Pieces is the best AI coding assistant. Finally, we set the value of the rank property to 1, a number type. We see that all the values in the companyObject conform to the initially defined types.

Differences between Types and Interfaces

Based on what we have discussed so far, the major difference between type and interface in TypeScript is how they represent data, but that’s not all. In this section, we will further explore type vs interface in TypeScript and how they can be used to perform actions such as declaration merging, defining primitive types, defining union types, defining tuples, implementing classes, and extending the properties or methods of a class. Let’s dive in!

Declaration Merging

When two interfaces with the same name (but different properties) are declared, the TypeScript compiler automatically merges the two declarations. Here’s an example:

interface Company {
    name: string;
}

interface Company {
    description: string;
}

let companyObject: Company = {
  name: "Pieces for Developers";
  description: "an on-device AI coding assistant and snippet manager";
};

Here, the two Company interface declarations are automatically merged by the TypeScript compiler. This is not possible with the type keyword. Suppose you define multiple declarations with the same name using the type keyword:

type Company = {
  name: string
}

type Company = {
  description: string
}

The code above does not run successfully, it throws an error that says Duplicate identifier 'Company'. This shows that declaration merging only works with TypeScript interfaces.

Primitive Types

Primitive types refer to the fundamental types used in TypeScript. They include string, boolean, number, etc. Type aliases are often used to represent these primitive types. Consider the example below:

type name = string;
type rank = number;
type isBest = boolean;

Interfaces, on the other hand, cannot be used to represent a primitive type. They can only be used for an object type. So, to define a primitive type, you should use the type keyword.

Union Types

Sometimes, you may need to create values that can be of multiple types, this can be achieved with union types. Union types can be defined using type aliases. Let’s see an example:

type profileName = string | number;

let profile: profileName = "James"; // this can also be of a number type

Here, we created a profileName that can be either a string type or a number type. Union types cannot be directly created using interfaces. You can, however, create a union type by combining two interfaces. Here’s an example:

interface firstName {
  first_name: "string"
};

interface lastName {
  last_name: "string"
};

type profileName = firstName | lastName;

Tuples

Tuples allow you to represent a fixed set of values (can be of different types) in an array. They provide a more structured way to deal with values of different data types. A tuple has a fixed length and an ordered position for each element, so object declarations must conform to the exact length of the array and must be in the exact arrangement. Consider this example to have a better understanding of Tuples:

type userDetails = [name: string, age: number, isRegistered: boolean];

Here, we have a tuple created with three elements in an array. We can then create an object that follows the structure defined above:

const user: userDetails = [“Ella", 26, true];

The elements in the array conform to the data types initially defined. “Ella” is of string type and is in the first position in the array, “26” is of number type and is in the second position, “true” is of boolean type and is in the third position.

Implementation of Classes

Classes in TypeScript can be implemented using either type alias or interface. Let’s see how to implement classes using type aliases or interfaces:

//type

type User = { name: string; greet: () => string; }; class User implements UserDetails { greet() { console.log('Hi there!'); } }

//interface

interface User = {

userID: string;

validate: () => string;

};

class User implements UserDetails {

greet() {

console.log('Registration successful');

}

}

The syntax for the implementation is fairly similar; the only downside is that union types cannot be implemented with class.

Extensions and Intersections

TypeScript allows you to extend the properties and methods of a particular interface type to a new interface. This is only for interfaces and can be implemented using the extends keyword. We can create a new interface by extending an existing interface, here’s an example:

interface Company {
name: string;
}

interface SupportTeam extends Company {
    ticketID: number;
    ticketname: string;
}

Here, we extended the properties from the Company interface and created a new SupportTeam interface. This new interface contains the properties of the parent interface and the new interface created. Type aliases do not provide an out-of-the-box functionality to extend classes. We can, however, use the intersection (&) operator to combine multiple types into one. Here’s an example:

type Company {
name: string;
}

The properties of type aliases and interfaces can be extended, however, the manner of implementation is different.

TypeScript Interface vs Types: Which Should You Use?

We’ve discussed TypeScript type vs interface, and how they differ. We also discussed how the usage of type aliases, instead of interfaces, could throw an error. Now, let’s explore specific instances when you should use types and interfaces. Here are some use cases of types:

  • To define primitive types

  • To overload functions

  • To use advanced features such as mapped types, utility types, conditional types, etc.

  • To create aliases for complex types such as unions and intersections

  • To define tuple types

Types, compared to interfaces, provide a wider range of type handling. Here are some use cases of interfaces:

  • Defining object methods or object shapes

  • Merging declarations

  • Extending properties or methods of interfaces

Conclusion

As a developer, it’s highly beneficial to follow best practices when coding. This helps create scalable applications, make code documentation easier, and foster collaboration among developers. In this blog post, we discussed TypeScript interface vs type, their differences, and the specific use cases of each. Happy coding!