Loading...

Interface vs Type alias in Typescript with some real-world examples showing when to use what

question typescript
Ram Patra Published on July 28, 2024

In TypeScript, both interface and type alias can be used to define the shape of an object. However, there are some differences and nuances between them. Here are the key differences:

1. Declaration and Syntax

  • Interface:

    • Declared using the interface keyword.
    • Primarily used to describe the shape of objects.
    • Can be extended using the extends keyword.
interface User {
    id: number;
    name: string;
    email: string;
}
  • Type Alias:

    • Declared using the type keyword.
    • Can represent any type, including primitives, unions, tuples, and intersections.
type User = {
    id: number;
    name: string;
    email: string;
};

2. Extending and Implementing

  • Interface:

    • Can be extended by other interfaces.
    • Can be implemented by classes.
interface User {
    id: number;
    name: string;
    email: string;
}

interface Admin extends User {
    isAdmin: boolean;
}

class UserAccount implements User {
    id: number;
    name: string;
    email: string;

    constructor(id: number, name: string, email: string) {
        this.id = id;
        this.name = name;
        this.email = email;
    }
}
  • Type Alias:

    • Can create new types using intersections and unions.
    • Cannot be implemented by classes directly.
type User = {
    id: number;
    name: string;
    email: string;
};

type Admin = User & {
    isAdmin: boolean;
};

const admin: Admin = {
    id: 1,
    name: "John Doe",
    email: "[email protected]",
    isAdmin: true
};

3. Merging Declarations

  • Interface:

    • Supports declaration merging. If you declare an interface with the same name multiple times, TypeScript will merge them into a single interface.
interface User {
    id: number;
    name: string;
}

interface User {
    email: string;
}

// Equivalent to:
// interface User {
//     id: number;
//     name: string;
//     email: string;
// }
  • Type Alias:

    • Does not support declaration merging. Declaring a type alias with the same name multiple times will result in a compiler error.
type User = {
    id: number;
    name: string;
};

type User = {
    email: string;
}; // Error: Duplicate identifier 'User'.

4. Complex Types

  • Type Alias:

    • More flexible in defining complex types, such as unions, intersections, and mapped types.
type StringOrNumber = string | number;

type Coordinates = [number, number];

type ReadonlyUser = {
    readonly id: number;
    name: string;
    email: string;
};
  • Interface:

    • Primarily used for objects. While it can be used with some complex types through extensions, it’s not as versatile as type aliases for unions and intersections.

5. Usage and Readability

  • Interface:

    • Generally preferred for defining the shape of objects and classes.
    • Provides a clear and semantic way to define object structures, making the code more readable.
  • Type Alias:

    • Preferred for more complex type definitions, such as union types and mapped types.
    • Offers more flexibility but can sometimes make the code less readable if overused.

Summary

  • Use interface when you want to define the structure of an object and benefit from features like declaration merging and class implementation.
  • Use type alias for defining more complex types, such as unions, intersections, and mapped types, or when you need more flexibility in type definitions.

Now, some concrete, real-life examples that illustrate when to use interfaces and when to use type aliases in TypeScript:

1. Interfaces for Object Shapes and Class Implementations

Use Case: Defining the Shape of an Object

When you want to define the structure of an object that is passed around in your application, using an interface is a clear and semantic choice.

interface User {
    id: number;
    name: string;
    email: string;
}

function getUserInfo(user: User): void {
    console.log(`User ID: ${user.id}`);
    console.log(`User Name: ${user.name}`);
    console.log(`User Email: ${user.email}`);
}

const user: User = {
    id: 1,
    name: "Alice",
    email: "[email protected]"
};

getUserInfo(user);

Use Case: Class Implementation

When defining classes that need to adhere to a specific structure, interfaces are very useful.

interface Vehicle {
    make: string;
    model: string;
    year: number;
    startEngine(): void;
}

class Car implements Vehicle {
    make: string;
    model: string;
    year: number;

    constructor(make: string, model: string, year: number) {
        this.make = make;
        this.model = model;
        this.year = year;
    }

    startEngine(): void {
        console.log(`${this.make} ${this.model} engine started.`);
    }
}

const myCar = new Car('Toyota', 'Corolla', 2020);
myCar.startEngine();

2. Type Aliases for Unions, Intersections, and Complex Types

Use Case: Union Types

When you need to define a variable that can hold multiple types, type aliases are ideal.

type Status = 'success' | 'error' | 'loading';

function printStatus(status: Status): void {
    console.log(`Current status: ${status}`);
}

printStatus('success'); // Valid
printStatus('error');   // Valid
printStatus('loading'); // Valid

Use Case: Intersection Types

Type aliases are great for combining multiple types into one.

type Identifiable = {
    id: number;
};

type Nameable = {
    name: string;
};

type User = Identifiable & Nameable;

const user: User = {
    id: 1,
    name: 'Alice'
};

console.log(user);

Use Case: Tuples and Arrays

For defining tuples and array types, type aliases are straightforward.

type Point = [number, number];

const point: Point = [10, 20];
console.log(point);

type StringArray = string[];

const fruits: StringArray = ['apple', 'banana', 'cherry'];
console.log(fruits);

3. Advanced Examples

Use Case: Conditional Types and Mapped Types

When you need more advanced type manipulation, type aliases are the way to go.

type ApiResponse<T> = {
    status: number;
    data: T;
};

type User = {
    id: number;
    name: string;
    email: string;
};

type UserResponse = ApiResponse<User>;

const response: UserResponse = {
    status: 200,
    data: {
        id: 1,
        name: 'Alice',
        email: '[email protected]'
    }
};

console.log(response);

type ReadOnly<T> = {
    readonly [P in keyof T]: T[P];
};

type ReadOnlyUser = ReadOnly<User>;

const user: ReadOnlyUser = {
    id: 1,
    name: 'Alice',
    email: '[email protected]'
};

// user.id = 2; // Error: Cannot assign to 'id' because it is a read-only property.

Use Case: Function Types

For defining function signatures, type aliases provide a concise way to represent the function type.

type Greet = (name: string) => string;

const greet: Greet = (name) => `Hello, ${name}!`;

console.log(greet('Alice'));

Summary

  • Use interface when:

    • Defining the structure of objects.
    • Creating contracts for classes to implement.
    • You need declaration merging.
  • Use type alias when:

    • Defining union or intersection types.
    • Creating complex types like tuples, arrays, and mapped types.
    • You need to represent more than just object shapes, such as function types, conditional types, etc.

By choosing the appropriate construct, you can make your TypeScript code more maintainable, readable, and expressive.

Presentify

Take your presentation to the next level.

FaceScreen

Put your face and name on your screen.

ToDoBar

Your to-dos on your menu bar.

Ram Patra Published on July 28, 2024
Image placeholder

Keep reading

If this article was helpful, others might be too

question typescript July 28, 2024 How to create a JSON Object in Typescript?

Creating a JSON object in TypeScript is similar to how you would create one in JavaScript. Here are the steps you can follow:

question typescript javascript July 17, 2024 Difference between ?? and || in Typescript or Javascript?

In TypeScript (and JavaScript), the ?? (nullish coalescing operator) and || (logical OR operator) are both used to provide default values, but they behave differently in terms of the conditions under which they return the right-hand operand.

question eslint nextjs November 14, 2024 How to disable ESLint in a Nextjs project?

Although it is highly advisable to enable ESLint at all times, if you do not want ESLint to run during next build, you can set the eslint.ignoreDuringBuilds option in next.config.js to true like below: