DEV Community

Toluwanimi Isaiah
Toluwanimi Isaiah

Posted on • Edited on

Type Aliases vs Interfaces in TypeScript

TypeScript provides two ways to create custom types for our data and they include Type aliases and Interfaces. This article will discuss the similarities and differences between them, and the best use cases for each. Let's dive in!

Type aliases

A type alias is basically a name for any type. Type aliases can be used to represent not only primitives but also object types, union types, tuples and intersections. Let's take a look at some examples:

type userName = string;
type userId = string | number; // Union type
type arr = number[];

// Object type
type Person = {
    id: userId; // We're making use of another type alias here
    name: userName;
    age: number;
    gender: string;
    isWebDev: boolean;
};

const user: Person = {
    id: 12345,
    name: "Tolu",
    age: 1,
    gender: "female",
    isWebDev: true,
};

const numbers: arr = [1, 8, 9];
Enter fullscreen mode Exit fullscreen mode

Type aliases are declared with the type keyword preceding them. Think of them like regular JavaScript variables that are used to represent certain values. Wherever you use a variable name, it is evaluated to the value that it represents. Type aliases work in a similar manner. Wherever you annotate a type by its alias, the alias will evaluate to the type(s) that it stands for. Like variables, you can't declare the same type alias more than once. Aliases are also never inferred by TypeScript, you have to explicitly annotate them.

Interfaces

Interfaces are another way to name data structures e.g objects. The declaration syntax for interfaces is different from that of type aliases. Let's rewrite the type alias Person above to an interface declaration:

interface Person {
    id: userId;
    name: userName;
    age: number;
    gender: string;
    isWebDev: boolean;
}
Enter fullscreen mode Exit fullscreen mode

Unions and Intersections

A union type is formed from two or more other types, and represents a value that can be any one of them. Intersections allow us to combine multiple existing types into a single type that has all the features of those types. Take the following example:

type Human = {
    name: string;
    speaks: boolean;
};

interface Dog {
    name: string;
    barks: boolean;
}

type HumanAndDog = Human & Dog; // Intersection
type HumanOrDogOrBoth = Human | Dog; // Union

let humanAndDog: HumanAndDog = {
    // must have all the properties of Human and Dog
    name: "Sparky",
    speaks: false,
    barks: true,
};

let humanOrDog: HumanOrDogOrBoth = {
    // can have the properties of Human or Dog, or both
    name: "Tolu",
    speaks: true,
};
Enter fullscreen mode Exit fullscreen mode

Type aliases and interfaces can be combined into one type using unions or intersections, but cannot be combined into an interface.

Tuples

Tuples are a way to type arrays with fixed lengths, accounting for every item in said array.

type Mix = [number, string, boolean];

const mix: Mix = [1, "banana", true];
Enter fullscreen mode Exit fullscreen mode

In TypeScript, tuples can be declared with types but not interfaces. However, tuples can be used inside interfaces, like this:

interface Mix {
    a: [number, string, boolean];
}
Enter fullscreen mode Exit fullscreen mode

Declaration merging

If you declare one interface with the same name more than once, TypeScript merges them into one declaration and will treat them as a single interface. This is called declaration merging.

interface Person {
    name: string;
}

interface Person {
    age: number;
}

type Num = numbers;
type Num = string; // Duplicate identifier Num

let user: Person = {
    // has the properties of both instances of Person
    name: "Tolu",
    age: 0,
};
Enter fullscreen mode Exit fullscreen mode

Declaration merging only works on interfaces. TypeScript will give an error if you declare the same type alias more than once.

Extends

Both type aliases and interfaces can be extended. However, the syntax differs. A derived type alias or interface has all the properties and methods of its base type alias or interface, and can also define additional members.

A type alias can extend another type alias using an ampersand:

type A = { x: number };
type B = A & { y: string };
Enter fullscreen mode Exit fullscreen mode

Type aliases can also extend interfaces:

interface A {
    x: number;
}

type B = A & { y: string };
Enter fullscreen mode Exit fullscreen mode

Interfaces can extend type aliases with the extends keyword:

type A = {
    x: number;
};

interface B extends A {
    y: string;
}
Enter fullscreen mode Exit fullscreen mode

Interfaces can extend other interfaces the same way they extend type aliases. Interfaces can also extend multiple interfaces separated by commas.

interface A {
    x: string;
    y: number;
}

interface B {
    z: boolean;
}

interface C extends A, B {
    breed: string;
}

let c: C = {
    x: "Sparky",
    y: 4,
    z: true,
};
Enter fullscreen mode Exit fullscreen mode

Interfaces extending classes

TypeScript allows an interface to extend a class. When this happens, the interface inherits the members of the base class, including the private and public members, but not their implementations. This means that when you create an interface that extends a class with private members, only that class or its subclasses can implement that interface. This allows you to restrict the use of the interface to the class or subclasses of the class that it extends.

class Base {
    greetFriend() {
        console.log(`Hello!`);
    }
}

// Interface extending the Base class
interface Derived extends Base {
    giveGist(): void;
}

// New class that extends Base class and implements the Derived interface
class NewClass extends Base implements Derived {
    giveGist() {
        console.log("I saw this the other day...");
    }
}

const c = new NewClass();
c.greetFriend(); // Hello!
c.giveGist(); // I saw this the other day...
Enter fullscreen mode Exit fullscreen mode

Implements

TypeScript supports class-based object-oriented programming. As a result, it allows classes to implement both type aliases and interfaces using the implements keyword. An error will be thrown if a class fails to correctly implement it.

// Interface being implemented by a class
interface A {
    x: number;
    y: number;
}

class SomeClass implements A {
    x = 1;
    y = 2;
}

// Type alias being implemented by a class
type B = {
    x: number;
    y: number;
};

class SomeClass2 implements B {
    x = 1;
    y = 2;
}
Enter fullscreen mode Exit fullscreen mode

A class can also implement multiple interfaces separated by commas e.g class A implements B, C {}. Note that a class can not implement or extend a type alias that represents a union type:

type C = { x: number } | { y: number };

// ERROR: A class can only implement an object type or intersection of object types with statically known members.
class SomeClass3 implements C {
    x = 1;
    y = 2;
}
Enter fullscreen mode Exit fullscreen mode

It’s important to understand that an implements clause is only a check that the class can be treated as the interface type. It doesn’t change the type of the class or its methods at all. A common source of error is to assume that an implements clause will change the class type - it doesn’t!
TypeScript official docs

Which should I use?

Type aliases and interfaces are very similar and you can choose freely between them. Personally, I use type aliases when defining primitive, union, intersection, function or tuple types. However, I make use of interfaces when defining object types or to take advantage of declaration merging.

Conclusion

We have seen the differences and similarities between type aliases and interfaces.

  • Both type aliases and interfaces can be used to describe the shape of an object or a function signature. But the syntax differs.
  • Declaration merging only works on interfaces and not type aliases.
  • You cannot declare a union, intersection or tuple with the interface keyword. However, you can make use of them within an interface.
  • Classes can implement both type aliases and interfaces, but not type aliases that represent a union type.

I hope that you have gained something useful from this article. Kindly leave any questions or additional information in the comment section.

Thanks for reading!

Top comments (1)

Collapse
 
jlaw6809 profile image
jlawrence6809

Interfaces can in some cases be more performant and provide cleaner type checking messages:
github.com/microsoft/TypeScript/wi...