TypeScript Type Aliases vs Interfaces

Welcome to The Coding College, where we simplify programming concepts for developers of all skill levels. At The Coding College, our mission is to empower you with knowledge to write clean and efficient code. In today’s post, we’ll explore Type Aliases and Interfaces in TypeScript, highlighting their differences, use cases, and best practices.

What Are Type Aliases and Interfaces in TypeScript?

Both type aliases and interfaces are tools for defining custom types in TypeScript. They allow you to describe the structure of an object, a function, or even more complex constructs. While they often overlap in functionality, each has its unique strengths and use cases.

Type Aliases

A type alias is a way to create a custom type with a name. You can use it for objects, primitives, unions, intersections, and more.

Syntax

type AliasName = definition;

Example: Basic Object

type User = {
    name: string;
    age: number;
    isActive: boolean;
};

let user: User = { name: "Alice", age: 30, isActive: true };
console.log(user.name); // Output: Alice

Example: Union and Intersection Types

Type aliases are especially useful for unions and intersections.

type Status = "active" | "inactive" | "pending";
let currentStatus: Status = "active";

type Address = { city: string; zip: number };
type Contact = { phone: string };
type Profile = Address & Contact;

let profile: Profile = { city: "New York", zip: 10001, phone: "123-456-7890" };

Interfaces

An interface is a TypeScript construct to define the structure of objects. Unlike type aliases, interfaces are primarily designed for object shapes and can be extended.

Syntax

interface InterfaceName {
    propertyName: type;
}

Example: Basic Object

interface User {
    name: string;
    age: number;
    isActive: boolean;
}

let user: User = { name: "Bob", age: 25, isActive: true };
console.log(user.age); // Output: 25

Example: Extending Interfaces

Interfaces can extend other interfaces to reuse and combine structures.

interface Address {
    city: string;
    zip: number;
}

interface Employee extends Address {
    name: string;
    role: string;
}

let employee: Employee = { name: "John", role: "Manager", city: "Boston", zip: 02115 };

Key Differences Between Type Aliases and Interfaces

FeatureType AliasInterface
Primary UseWorks with objects, unions, intersections, and primitivesPrimarily for object shapes
ExtensibilityCannot extend after creationCan extend using extends
Union and IntersectionSupportedNot supported
Declaration MergingNot supportedSupported (allows multiple declarations)
ClassesRarely usedCommonly used to define class shapes

When to Use Type Aliases

1. For Union and Intersection Types

Type aliases are the only way to represent union and intersection types.

type Result = "success" | "failure";
type DetailedError = { message: string } & { code: number };

2. For Complex Combinations

If your type requires a mix of objects, arrays, or primitive types, use a type alias.

type ApiResponse = { status: number; data: any } | { error: string };

3. For Primitive Wrappers

Type aliases are ideal for naming custom types derived from primitives.

type ID = string;
type Price = number;

When to Use Interfaces

1. For Object Shapes

Interfaces are perfect for defining the structure of an object.

interface Car {
    make: string;
    model: string;
    year: number;
}

2. When Extensibility Is Required

Interfaces allow you to extend and compose types.

interface Person {
    name: string;
    age: number;
}

interface Employee extends Person {
    role: string;
}

3. For Classes

Interfaces work seamlessly with classes to define contracts.

interface Shape {
    area(): number;
}

class Circle implements Shape {
    radius: number;
    constructor(radius: number) {
        this.radius = radius;
    }
    area(): number {
        return Math.PI * this.radius * this.radius;
    }
}

Advanced Features

Declaration Merging with Interfaces

Interfaces support merging declarations, which is useful for adding new properties to existing interfaces.

interface User {
    name: string;
}

interface User {
    age: number;
}

let user: User = { name: "Alice", age: 25 }; // Merged structure

Generics in Type Aliases and Interfaces

Both type aliases and interfaces support generics.

Example: Type Alias

type Wrapper<T> = { value: T };
let stringWrapper: Wrapper<string> = { value: "Hello" };

Example: Interface

interface Wrapper<T> {
    value: T;
}
let numberWrapper: Wrapper<number> = { value: 42 };

Common Mistakes

1. Overusing Type Aliases for Object Shapes

While type aliases work for objects, interfaces are generally preferred for clarity and extensibility.

// Prefer interfaces for object shapes:
interface User { name: string; age: number; }

2. Confusing Union Types with Interfaces

Interfaces cannot represent union types.

// Use type alias for unions:
type Status = "active" | "inactive";

Best Practices

  1. Use Interfaces for Objects: For defining structured objects, interfaces are clearer and easier to extend.
  2. Use Type Aliases for Primitives and Unions: When working with unions, intersections, or primitives, type aliases are the way to go.
  3. Combine Both When Necessary: There’s no harm in mixing both type aliases and interfaces in the same codebase, depending on the use case.

Conclusion

Understanding when to use Type Aliases and Interfaces is key to writing clean and maintainable TypeScript code. While they share similarities, their differences make them uniquely suited for specific scenarios.

Leave a Comment