From Zero to AI

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 interface for objects that represent things (User, Product, Order)
  • Use type for 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

  1. Type aliases give names to types: Use them for readability and reusability
  2. Union types need type aliases: You cannot create unions with interfaces
  3. Intersection types combine types: Use & to merge multiple types
  4. Tuples are typed arrays: Fixed length with specific types per position
  5. Type aliases work for functions: Create reusable function signatures
  6. Choose based on use case: Interfaces for objects, type aliases for unions and combinations
  7. 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.

Continue to Lesson 3.6: Optional and Readonly Properties