Skip to main content
โšก Calmops

Interfaces and Type Aliases in TypeScript

Interfaces and Type Aliases in TypeScript

Interfaces and type aliases are core TypeScript features for defining types. This article covers their differences, use cases, and best practices.

Introduction

Interfaces and type aliases provide:

  • Type definition
  • Code documentation
  • Structural typing
  • Reusability
  • Maintainability

Understanding these helps you:

  • Define custom types
  • Create contracts
  • Extend types
  • Compose types
  • Write maintainable code

Type Aliases

Basic Type Aliases

// โœ… Good: Basic type aliases
type Name = string;
type Age = number;
type IsActive = boolean;

const name: Name = 'John';
const age: Age = 30;
const active: IsActive = true;

// โœ… Good: Union type aliases
type Status = 'active' | 'inactive' | 'pending';
type ID = string | number;

const status: Status = 'active';
const userId: ID = 123;

// โœ… Good: Object type aliases
type User = {
  name: string;
  age: number;
  email: string;
};

const user: User = {
  name: 'John',
  age: 30,
  email: '[email protected]'
};

// โœ… Good: Function type aliases
type Callback = (value: string) => void;
type Transformer = (value: string) => number;

const callback: Callback = (value) => console.log(value);
const transformer: Transformer = (value) => value.length;

Type Alias Features

// โœ… Good: Intersection types
type Named = { name: string };
type Aged = { age: number };
type Person = Named & Aged;

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

// โœ… Good: Conditional types
type IsString<T> = T extends string ? true : false;
type A = IsString<'hello'>; // true
type B = IsString<42>; // false

// โœ… Good: Mapped types
type Readonly<T> = {
  readonly [K in keyof T]: T[K];
};

type ReadonlyUser = Readonly<User>;

Interfaces

Basic Interfaces

// โœ… Good: Basic interface
interface User {
  name: string;
  age: number;
  email: string;
}

const user: User = {
  name: 'John',
  age: 30,
  email: '[email protected]'
};

// โœ… Good: Optional properties
interface Config {
  host: string;
  port?: number;
  timeout?: number;
}

const config: Config = {
  host: 'localhost'
};

// โœ… Good: Readonly properties
interface Point {
  readonly x: number;
  readonly y: number;
}

const point: Point = { x: 10, y: 20 };
// point.x = 30; // Error

// โœ… Good: Method signatures
interface Calculator {
  add(a: number, b: number): number;
  subtract(a: number, b: number): number;
}

const calc: Calculator = {
  add: (a, b) => a + b,
  subtract: (a, b) => a - b
};

Interface Extension

// โœ… Good: Extend interfaces
interface Named {
  name: string;
}

interface Aged {
  age: number;
}

interface Person extends Named, Aged {
  email: string;
}

const person: Person = {
  name: 'John',
  age: 30,
  email: '[email protected]'
};

// โœ… Good: Override properties
interface Animal {
  name: string;
  age: number;
}

interface Dog extends Animal {
  breed: string;
  age: number; // Can override
}

// โœ… Good: Merge interfaces
interface User {
  name: string;
}

interface User {
  age: number;
}

// Merged interface
const user: User = {
  name: 'John',
  age: 30
};

Interfaces vs Type Aliases

Comparison

// โœ… Good: Understanding differences

// Type aliases can be unions
type Status = 'active' | 'inactive';
// interface Status = 'active' | 'inactive'; // Error

// Interfaces can be merged
interface User {
  name: string;
}
interface User {
  age: number;
}
// Type aliases cannot be merged

// Both can extend
interface Animal {
  name: string;
}

interface Dog extends Animal {
  breed: string;
}

type Cat = Animal & {
  color: string;
};

// Both can be used for objects
interface UserInterface {
  name: string;
}

type UserType = {
  name: string;
};

// Both work the same for objects
const user1: UserInterface = { name: 'John' };
const user2: UserType = { name: 'Jane' };

When to Use Each

// โœ… Good: Use type aliases for unions
type Status = 'active' | 'inactive' | 'pending';

// โœ… Good: Use interfaces for object contracts
interface User {
  name: string;
  age: number;
}

// โœ… Good: Use interfaces for class implementation
interface Animal {
  name: string;
  makeSound(): void;
}

class Dog implements Animal {
  name: string;

  constructor(name: string) {
    this.name = name;
  }

  makeSound() {
    console.log('Woof!');
  }
}

// โœ… Good: Use type aliases for complex types
type Result<T> = { success: true; data: T } | { success: false; error: string };

Practical Examples

API Response Types

// โœ… Good: Type-safe API responses
interface ApiResponse<T> {
  status: 'success' | 'error';
  data?: T;
  error?: string;
  timestamp: number;
}

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

interface Post {
  id: number;
  title: string;
  content: string;
  authorId: number;
}

type UserResponse = ApiResponse<User>;
type PostResponse = ApiResponse<Post>;
type UsersResponse = ApiResponse<User[]>;

async function fetchUser(id: number): Promise<UserResponse> {
  // Implementation
  return {
    status: 'success',
    data: { id: 1, name: 'John', email: '[email protected]' },
    timestamp: Date.now()
  };
}

Service Interfaces

// โœ… Good: Service interfaces
interface IUserService {
  getUser(id: number): Promise<User>;
  createUser(data: Omit<User, 'id'>): Promise<User>;
  updateUser(id: number, data: Partial<User>): Promise<User>;
  deleteUser(id: number): Promise<void>;
}

interface IAuthService {
  login(username: string, password: string): Promise<string>;
  logout(): Promise<void>;
  verify(token: string): Promise<boolean>;
}

class UserService implements IUserService {
  async getUser(id: number): Promise<User> {
    // Implementation
    return { id, name: 'John', email: '[email protected]' };
  }

  async createUser(data: Omit<User, 'id'>): Promise<User> {
    // Implementation
    return { id: 1, ...data };
  }

  async updateUser(id: number, data: Partial<User>): Promise<User> {
    // Implementation
    return { id, name: 'John', email: '[email protected]', ...data };
  }

  async deleteUser(id: number): Promise<void> {
    // Implementation
  }
}

Generic Interfaces

// โœ… Good: Generic interfaces
interface Repository<T> {
  getAll(): Promise<T[]>;
  getById(id: number): Promise<T | null>;
  create(data: Omit<T, 'id'>): Promise<T>;
  update(id: number, data: Partial<T>): Promise<T>;
  delete(id: number): Promise<void>;
}

interface Entity {
  id: number;
}

interface User extends Entity {
  name: string;
  email: string;
}

interface Post extends Entity {
  title: string;
  content: string;
}

class UserRepository implements Repository<User> {
  async getAll(): Promise<User[]> {
    // Implementation
    return [];
  }

  async getById(id: number): Promise<User | null> {
    // Implementation
    return null;
  }

  async create(data: Omit<User, 'id'>): Promise<User> {
    // Implementation
    return { id: 1, ...data };
  }

  async update(id: number, data: Partial<User>): Promise<User> {
    // Implementation
    return { id, name: 'John', email: '[email protected]', ...data };
  }

  async delete(id: number): Promise<void> {
    // Implementation
  }
}

Best Practices

  1. Use interfaces for object contracts:

    // โœ… Good
    interface User {
      name: string;
      age: number;
    }
    
    // โŒ Bad
    type User = {
      name: string;
      age: number;
    };
    
  2. Use type aliases for unions:

    // โœ… Good
    type Status = 'active' | 'inactive';
    
    // โŒ Bad
    interface Status {
      value: 'active' | 'inactive';
    }
    
  3. Use interfaces for class implementation:

    // โœ… Good
    interface Animal {
      name: string;
    }
    
    class Dog implements Animal {
      name: string;
    }
    
    // โŒ Bad
    type Animal = { name: string };
    class Dog implements Animal { } // Error
    

Common Mistakes

  1. Using type aliases for everything:

    // โŒ Bad
    type User = { name: string };
    type Config = { host: string };
    
    // โœ… Good
    interface User { name: string }
    interface Config { host: string }
    
  2. Not extending interfaces:

    // โŒ Bad
    interface User {
      name: string;
      age: number;
      email: string;
    }
    
    interface Admin {
      name: string;
      age: number;
      email: string;
      role: string;
    }
    
    // โœ… Good
    interface User {
      name: string;
      age: number;
      email: string;
    }
    
    interface Admin extends User {
      role: string;
    }
    

Summary

Interfaces and type aliases are essential. Key takeaways:

  • Use type aliases for unions
  • Use interfaces for object contracts
  • Extend interfaces for reuse
  • Use generics for flexibility
  • Implement interfaces in classes
  • Merge interfaces when needed
  • Choose appropriate tool for task
  • Document with types

Next Steps

Comments