Skip to main content

Interfaces and Type Aliases in TypeScript

Created: May 8, 2026 Larry Qu 7 min read

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;
    };
    ```javascript
    
  2. Use type aliases for unions:
    // ✅ Good
    type Status = 'active' | 'inactive';
    
    // ❌ Bad
    interface Status {
      value: 'active' | 'inactive';
    }
    ```javascript
    
  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
    ```javascript
    

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 }
    ```javascript
    
  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

Resources

Comments

Share this article

Scan to read on mobile