Lesson 3.5: Type Aliases
Duration: 50 minutes
Learning Objectives
By the end of this lesson, you will be able to:
- Create type aliases for any type
- Understand the difference between type aliases and interfaces
- Use type aliases for union types and complex type combinations
- Choose between type aliases and interfaces appropriately
What is a Type Alias?
A type alias creates a new name for any type. It does not create a new type - it gives an existing type (or type combination) a convenient name.
type UserId = string;
type Age = number;
type IsActive = boolean;
let userId: UserId = 'user_123';
let userAge: Age = 28;
let active: IsActive = true;
Basic Type Aliases
Primitive Aliases
Give meaningful names to primitive types:
type Email = string;
type Price = number;
type Timestamp = number;
const userEmail: Email = 'alice@example.com';
const productPrice: Price = 99.99;
const createdAt: Timestamp = Date.now();
While Email is still just a string, the alias documents your intent.
Array Aliases
type StringList = string[];
type NumberArray = number[];
type UserIds = string[];
const names: StringList = ['Alice', 'Bob', 'Charlie'];
const scores: NumberArray = [95, 87, 92];
const ids: UserIds = ['user_1', 'user_2', 'user_3'];
Type Aliases for Union Types
This is where type aliases really shine. You can give names to complex union types:
type Status = 'pending' | 'approved' | 'rejected';
type Result = 'success' | 'error';
type Id = string | number;
let orderStatus: Status = 'pending';
let apiResult: Result = 'success';
let recordId: Id = 'abc123';
recordId = 456; // Also valid
Without aliases, you would have to repeat the union everywhere:
// Without alias - repetitive and error-prone
function processOrder(status: 'pending' | 'approved' | 'rejected') {}
function displayStatus(status: 'pending' | 'approved' | 'rejected') {}
function updateStatus(status: 'pending' | 'approved' | 'rejected') {}
// With alias - clean and consistent
type Status = 'pending' | 'approved' | 'rejected';
function processOrder(status: Status) {}
function displayStatus(status: Status) {}
function updateStatus(status: Status) {}
Type Aliases for Objects
Type aliases can define object shapes, similar to interfaces:
type User = {
id: string;
name: string;
email: string;
age: number;
};
const alice: User = {
id: 'user_1',
name: 'Alice',
email: 'alice@example.com',
age: 28,
};
Nested Object Types
type Address = {
street: string;
city: string;
country: string;
};
type Person = {
name: string;
address: Address;
};
const employee: Person = {
name: 'Bob',
address: {
street: '123 Main St',
city: 'New York',
country: 'USA',
},
};
Intersection Types
Type aliases can combine multiple types using the intersection operator (&):
type HasId = {
id: string;
};
type HasTimestamps = {
createdAt: Date;
updatedAt: Date;
};
type Entity = HasId & HasTimestamps;
// Entity must have all properties from both types
const entity: Entity = {
id: 'entity_1',
createdAt: new Date(),
updatedAt: new Date(),
};
Combining Object Types
type PersonInfo = {
name: string;
age: number;
};
type ContactInfo = {
email: string;
phone: string;
};
type FullProfile = PersonInfo & ContactInfo;
const profile: FullProfile = {
name: 'Alice',
age: 28,
email: 'alice@example.com',
phone: '555-0123',
};
Type Aliases for Functions
type Callback = (data: string) => void;
type Calculator = (a: number, b: number) => number;
type Predicate = (value: number) => boolean;
const logMessage: Callback = (data) => {
console.log(data);
};
const add: Calculator = (a, b) => a + b;
const multiply: Calculator = (a, b) => a * b;
const isEven: Predicate = (value) => value % 2 === 0;
const isPositive: Predicate = (value) => value > 0;
Type Aliases for Tuples
Tuples are fixed-length arrays where each position has a specific type:
type Coordinate = [number, number];
type NameAge = [string, number];
type RGB = [number, number, number];
const point: Coordinate = [10, 20];
const person: NameAge = ['Alice', 28];
const red: RGB = [255, 0, 0];
Tuples are useful for returning multiple values:
type ParseResult = [boolean, string];
function parseInput(input: string): ParseResult {
if (input.length > 0) {
return [true, input.trim()];
}
return [false, ''];
}
const [success, value] = parseInput(' hello ');
console.log(success); // true
console.log(value); // "hello"
Type Alias vs Interface
Both can define object shapes, but they have differences:
What Only Interfaces Can Do
Declaration Merging:
interface User {
name: string;
}
interface User {
age: number;
}
// User now has both name and age
const user: User = { name: 'Alice', age: 28 };
Type aliases cannot do this - redeclaring would cause an error.
What Only Type Aliases Can Do
Union Types:
type Status = 'active' | 'inactive' | 'pending';
// Cannot create union types with interface
Primitive Aliases:
type ID = string;
// Cannot alias primitives with interface
Tuple Types:
type Point = [number, number];
// Cannot create tuples with interface
Mapped Types (advanced):
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
// Cannot use mapped types with interface directly
When to Use Each
| Use Case | Recommendation |
|---|---|
| Object shapes that might be extended | Interface |
| Union types | Type alias |
| Primitive aliases | Type alias |
| Tuples | Type alias |
| Function types | Either (type alias slightly more readable) |
| Public API contracts | Interface |
| Complex type combinations | Type alias |
Practical Guidelines
Many teams adopt a simple rule:
- Use
interfacefor objects that represent things (User, Product, Order) - Use
typefor everything else (unions, tuples, function types, etc.)
// Interface for domain objects
interface User {
id: string;
name: string;
email: string;
}
// Type alias for unions and combinations
type UserRole = 'admin' | 'editor' | 'viewer';
type UserWithRole = User & { role: UserRole };
Practical Examples
Example 1: API Response Types
type ApiStatus = 'idle' | 'loading' | 'success' | 'error';
type ApiError = {
code: number;
message: string;
};
type ApiResponse<T> = {
status: ApiStatus;
data: T | null;
error: ApiError | null;
};
type User = {
id: string;
name: string;
};
// Using the types
const response: ApiResponse<User[]> = {
status: 'success',
data: [
{ id: '1', name: 'Alice' },
{ id: '2', name: 'Bob' },
],
error: null,
};
Example 2: Event System
type EventType = 'click' | 'hover' | 'scroll' | 'keypress';
type EventHandler = (event: Event) => void;
type EventMap = {
[key in EventType]?: EventHandler[];
};
const handlers: EventMap = {
click: [(e) => console.log('clicked!')],
hover: [(e) => console.log('hovered!')],
};
Example 3: Configuration Options
type Environment = 'development' | 'staging' | 'production';
type LogLevel = 'debug' | 'info' | 'warn' | 'error';
type DatabaseConfig = {
host: string;
port: number;
name: string;
};
type AppConfig = {
env: Environment;
logLevel: LogLevel;
database: DatabaseConfig;
features: string[];
};
const config: AppConfig = {
env: 'development',
logLevel: 'debug',
database: {
host: 'localhost',
port: 5432,
name: 'myapp_dev',
},
features: ['darkMode', 'notifications'],
};
Exercises
Exercise 1: Create Status Types
Create type aliases for order processing:
- Order status (new, processing, shipped, delivered, cancelled)
- Payment status (pending, paid, refunded, failed)
- Combined order with both statuses
Solution
type OrderStatus = 'new' | 'processing' | 'shipped' | 'delivered' | 'cancelled';
type PaymentStatus = 'pending' | 'paid' | 'refunded' | 'failed';
type Order = {
id: string;
orderStatus: OrderStatus;
paymentStatus: PaymentStatus;
items: string[];
total: number;
};
const order: Order = {
id: 'ORD-001',
orderStatus: 'processing',
paymentStatus: 'paid',
items: ['item1', 'item2'],
total: 99.99,
};
Exercise 2: Intersection Types
Create types for a blog system using intersections:
- Base content (id, createdAt, updatedAt)
- Author info (authorId, authorName)
- Post with both plus title and content
Solution
type BaseContent = {
id: string;
createdAt: Date;
updatedAt: Date;
};
type AuthorInfo = {
authorId: string;
authorName: string;
};
type PostContent = {
title: string;
content: string;
tags: string[];
};
type BlogPost = BaseContent & AuthorInfo & PostContent;
const post: BlogPost = {
id: 'post_1',
createdAt: new Date(),
updatedAt: new Date(),
authorId: 'user_1',
authorName: 'Alice',
title: 'Getting Started with TypeScript',
content: 'TypeScript is awesome...',
tags: ['typescript', 'tutorial'],
};
Exercise 3: Function Types
Create type aliases for calculator operations:
Solution
type BinaryOperation = (a: number, b: number) => number;
type UnaryOperation = (a: number) => number;
const add: BinaryOperation = (a, b) => a + b;
const subtract: BinaryOperation = (a, b) => a - b;
const multiply: BinaryOperation = (a, b) => a * b;
const divide: BinaryOperation = (a, b) => a / b;
const square: UnaryOperation = (a) => a * a;
const negate: UnaryOperation = (a) => -a;
const double: UnaryOperation = (a) => a * 2;
console.log(add(5, 3)); // 8
console.log(square(4)); // 16
console.log(negate(10)); // -10
Exercise 4: Tuple Types
Create a tuple type for representing time (hours, minutes, seconds) and a function to format it:
Solution
type Time = [number, number, number]; // [hours, minutes, seconds]
function formatTime(time: Time): string {
const [hours, minutes, seconds] = time;
const pad = (n: number) => n.toString().padStart(2, '0');
return `${pad(hours)}:${pad(minutes)}:${pad(seconds)}`;
}
function addSeconds(time: Time, seconds: number): Time {
let [h, m, s] = time;
s += seconds;
m += Math.floor(s / 60);
s = s % 60;
h += Math.floor(m / 60);
m = m % 60;
h = h % 24;
return [h, m, s];
}
const currentTime: Time = [14, 30, 45];
console.log(formatTime(currentTime)); // "14:30:45"
const newTime = addSeconds(currentTime, 3600);
console.log(formatTime(newTime)); // "15:30:45"
Key Takeaways
- Type aliases give names to types: Use them for readability and reusability
- Union types need type aliases: You cannot create unions with interfaces
- Intersection types combine types: Use
&to merge multiple types - Tuples are typed arrays: Fixed length with specific types per position
- Type aliases work for functions: Create reusable function signatures
- Choose based on use case: Interfaces for objects, type aliases for unions and combinations
- Type aliases cannot be merged: Unlike interfaces, redeclaring causes an error
Resources
| Resource | Type | Description |
|---|---|---|
| TypeScript Handbook: Type Aliases | Documentation | Official guide |
| TypeScript Handbook: Differences Between Type Aliases and Interfaces | Documentation | Comparison guide |
| TypeScript Playground | Tool | Experiment online |
Next Lesson
Now that you can create interfaces and type aliases, let us explore optional and readonly properties - important features for modeling real-world data accurately.