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
-
Use interfaces for object contracts:
// โ Good interface User { name: string; age: number; } // โ Bad type User = { name: string; age: number; }; -
Use type aliases for unions:
// โ Good type Status = 'active' | 'inactive'; // โ Bad interface Status { value: 'active' | 'inactive'; } -
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
-
Using type aliases for everything:
// โ Bad type User = { name: string }; type Config = { host: string }; // โ Good interface User { name: string } interface Config { host: string } -
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
Related Resources
Next Steps
- Learn about Generics
- Explore Decorators and Metadata
- Study Advanced Type Patterns
- Practice interface design
- Build type-safe applications
Comments