Type Aliases & Interfaces
Type Aliases & Interfaces
TypeScript provides two primary ways to define custom types: type aliases and interfaces. Both allow you to create reusable type definitions, but they have subtle differences in capabilities and use cases. Understanding when to use each is crucial for writing clean, maintainable TypeScript code.
Type Aliases
A type alias creates a new name for any type. You define it using the type keyword followed by the alias name and the type definition.
// Simple type alias for primitive
type UserID = string | number;
// Type alias for object shape
type User = {
id: UserID;
name: string;
email: string;
age?: number;
};
// Using the type alias
const user1: User = {
id: 123,
name: 'John Doe',
email: 'john@example.com'
};
const user2: User = {
id: 'abc-456',
name: 'Jane Smith',
email: 'jane@example.com',
age: 28
};
Type Aliases for Complex Types
Type aliases excel at representing complex type combinations:
// Union type alias
type Status = 'pending' | 'approved' | 'rejected';
// Tuple type alias
type Coordinate = [number, number];
// Function type alias
type MathOperation = (a: number, b: number) => number;
// Complex object with nested types
type Product = {
id: string;
name: string;
price: number;
category: 'electronics' | 'clothing' | 'food';
tags: string[];
metadata: {
createdAt: Date;
updatedAt: Date;
views: number;
};
};
// Using function type alias
const add: MathOperation = (a, b) => a + b;
const multiply: MathOperation = (a, b) => a * b;
// Using complex type
const laptop: Product = {
id: 'prod-001',
name: 'MacBook Pro',
price: 2499,
category: 'electronics',
tags: ['computer', 'apple', 'laptop'],
metadata: {
createdAt: new Date(),
updatedAt: new Date(),
views: 1250
}
};
Interfaces
An interface is a way to define the shape of an object type. Interfaces use the interface keyword and are specifically designed for object shapes.
// Interface definition
interface Person {
firstName: string;
lastName: string;
age: number;
email?: string; // Optional property
readonly id: string; // Cannot be modified after creation
}
// Using the interface
const person: Person = {
id: 'person-123',
firstName: 'Alice',
lastName: 'Johnson',
age: 30
};
// person.id = 'new-id'; // Error: Cannot assign to 'id' because it is a read-only property
// Method in interface
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
};
readonly for properties that should only be set during initialization. This helps prevent accidental modifications and makes your code more predictable.
Extending Interfaces
One of the most powerful features of interfaces is the ability to extend other interfaces using the extends keyword. This allows you to build complex type hierarchies.
// Base interface
interface Animal {
name: string;
age: number;
makeSound(): void;
}
// Extended interface
interface Dog extends Animal {
breed: string;
wagTail(): void;
}
// Interface extending multiple interfaces
interface Bird {
wingspan: number;
canFly: boolean;
}
interface FlyingAnimal extends Animal, Bird {
altitude: number;
}
// Implementation
const myDog: Dog = {
name: 'Buddy',
age: 5,
breed: 'Golden Retriever',
makeSound: () => console.log('Woof!'),
wagTail: () => console.log('*tail wagging*')
};
const eagle: FlyingAnimal = {
name: 'Eagle',
age: 3,
wingspan: 2.5,
canFly: true,
altitude: 1000,
makeSound: () => console.log('Screech!')
};
Intersection Types
Intersection types combine multiple types into one using the & operator. The resulting type has all properties from all combined types.
// Individual types
type Identifiable = {
id: string;
};
type Timestamped = {
createdAt: Date;
updatedAt: Date;
};
type Versioned = {
version: number;
};
// Intersection type combining all three
type Entity = Identifiable & Timestamped & Versioned;
// Usage
const article: Entity = {
id: 'article-001',
createdAt: new Date('2024-01-01'),
updatedAt: new Date('2024-02-14'),
version: 3
};
// Intersection with object literals
type Name = {
firstName: string;
lastName: string;
};
type Contact = {
email: string;
phone: string;
};
type Employee = Name & Contact & {
employeeId: string;
department: string;
};
const employee: Employee = {
firstName: 'Sarah',
lastName: 'Williams',
email: 'sarah@company.com',
phone: '+1234567890',
employeeId: 'EMP-789',
department: 'Engineering'
};
never type.
Type Aliases vs Interfaces: Key Differences
While both can define object shapes, there are important differences between type aliases and interfaces:
// Interfaces can be merged
interface Window {
title: string;
}
interface Window {
size: number;
}
// Both declarations merge into one
const window: Window = {
title: 'My Window',
size: 1024
};
// Type aliases CANNOT be merged
type Config = {
theme: string;
};
// Error: Duplicate identifier 'Config'
// type Config = {
// language: string;
// };
// Interface extension (using extends)
interface BaseUser {
id: string;
name: string;
}
interface AdminUser extends BaseUser {
permissions: string[];
}
// Type alias intersection (using &)
type BaseUserType = {
id: string;
name: string;
};
type AdminUserType = BaseUserType & {
permissions: string[];
};
// Both achieve similar results
const admin1: AdminUser = {
id: 'admin-1',
name: 'Admin User',
permissions: ['read', 'write', 'delete']
};
const admin2: AdminUserType = {
id: 'admin-2',
name: 'Admin User',
permissions: ['read', 'write', 'delete']
};
// Type aliases work with primitives, unions, tuples
type StringOrNumber = string | number;
type Coordinates = [number, number];
type Callback = () => void;
// Interfaces are limited to object shapes
// interface StringOrNumber = string | number; // Error!
// interface Coordinates = [number, number]; // Error!
// However, interfaces can represent functions
interface CallbackInterface {
(): void;
}
// Both work for function types
const callback1: Callback = () => console.log('Called');
const callback2: CallbackInterface = () => console.log('Called');
When to Use Type Aliases vs Interfaces
Here are practical guidelines for choosing between type aliases and interfaces:
// 1. Defining object shapes (especially public APIs)
interface ApiResponse {
data: unknown;
status: number;
message: string;
}
// 2. You might need declaration merging
interface CustomWindow {
myCustomProperty: string;
}
// 3. Working with classes (interfaces work naturally with implements)
interface Drawable {
draw(): void;
}
class Circle implements Drawable {
draw() {
console.log('Drawing circle');
}
}
// 4. Building extensible type hierarchies
interface Vehicle {
brand: string;
model: string;
}
interface Car extends Vehicle {
doors: number;
}
interface ElectricCar extends Car {
batteryCapacity: number;
}
// 1. Defining union types
type Result = 'success' | 'error' | 'pending';
// 2. Defining tuple types
type RGB = [number, number, number];
// 3. Complex type manipulations
type Nullable<T> = T | null;
type ReadonlyProps<T> = {
readonly [K in keyof T]: T[K];
};
// 4. Function types
type EventHandler = (event: Event) => void;
// 5. Mapped types and utility types
type Optional<T> = {
[K in keyof T]?: T[K];
};
// 6. Intersection types
type UserWithTimestamps = User & Timestamped & Identifiable;
Combining Type Aliases and Interfaces
You can mix and match type aliases and interfaces in the same codebase:
// Type alias for union
type UserRole = 'admin' | 'editor' | 'viewer';
// Interface using type alias
interface UserAccount {
id: string;
username: string;
role: UserRole; // Using type alias
metadata: UserMetadata;
}
// Type alias using interface
type UserMetadata = {
lastLogin: Date;
loginCount: number;
};
// Interface extending with intersection
interface PremiumUser extends UserAccount {
subscriptionLevel: 'basic' | 'pro' | 'enterprise';
}
// Type alias combining interface and type
type FullUserProfile = UserAccount & {
preferences: {
theme: 'light' | 'dark';
language: string;
};
notifications: boolean;
};
// Implementation
const premiumUser: PremiumUser = {
id: 'user-001',
username: 'john_doe',
role: 'admin',
subscriptionLevel: 'pro',
metadata: {
lastLogin: new Date(),
loginCount: 150
}
};
const fullProfile: FullUserProfile = {
id: 'user-002',
username: 'jane_smith',
role: 'editor',
metadata: {
lastLogin: new Date(),
loginCount: 75
},
preferences: {
theme: 'dark',
language: 'en'
},
notifications: true
};
- Create an interface
Bookwith properties:title,author,isbn(readonly), and optionalpublishedYear - Create a type alias
BookFormatas a union of 'hardcover', 'paperback', 'ebook', and 'audiobook' - Create an interface
PhysicalBookthat extendsBookand addsformat: BookFormatandpageCount: number - Create a type alias
DigitalBookusing intersection that combinesBookwith an object containingfileSize: numberandformat: 'ebook' | 'audiobook' - Create instances of both
PhysicalBookandDigitalBookwith sample data
Summary
- Type aliases use the
typekeyword and can represent any type (primitives, unions, tuples, objects, functions) - Interfaces use the
interfacekeyword and are specialized for object shapes - Interfaces support extension with the
extendskeyword and declaration merging - Type aliases support intersections with the
&operator for combining types - Choose interfaces for object shapes, public APIs, and extensible hierarchies
- Choose type aliases for unions, tuples, complex type manipulations, and function types
- Both can be combined in the same codebase for maximum flexibility