SAE Logo

TypeScript Type Aliases

Type aliases create a new name for an existing type, making your TypeScript code more readable, maintainable, and reusable. They don't create new types but instead provide a way to refer to types with more descriptive names and encapsulate complex type definitions.

Basic Type Alias Syntax

Type aliases use the type keyword followed by a name, equals sign, and the type definition. The syntax is straightforward: you declare the alias name and assign it any valid TypeScript type. Type aliases can be simple one-liners for basic types or complex multi-line definitions for intricate object structures.

type UserId = number;

// Using the type alias
const id: UserId = 123456;

This simple example might not seem very useful, but type aliases become powerful when you work with more complex types:

More Useful Type Alias

type Coordinates = {
  x: number;
  y: number;
};

// Using the type alias
function calculateDistance(point1: Coordinates, point2: Coordinates): number {
  return Math.sqrt(Math.pow(point2.x - point1.x, 2) + Math.pow(point2.y - point1.y, 2));
}

const start: Coordinates = { x: 0, y: 0 };
const end: Coordinates = { x: 3, y: 4 };

console.log(calculateDistance(start, end)); // 5

Union Types

Union types within aliases use the pipe symbol | to separate possible types, creating a type that accepts any of the specified options. This is perfect for variables that can legitimately hold different types of values, like API responses that return either data or error objects, or configuration values that accept multiple formats.

type ID = string | number;

function printID(id: ID) {
  console.log(`ID: ${id}`);

  // Type narrowing
  if (typeof id === "string") {
    console.log(id.toUpperCase());
  } else {
    console.log(id.toFixed(0));
  }
}

printID(101); // Valid
printID("202"); // Also valid
// printID(true); // Error: Type 'boolean' is not assignable to type 'ID'

Intersection Types

Intersection types combine multiple types using the ampersand symbol &, creating a new type that must satisfy all constituent types simultaneously. They're useful for mixing object types together, adding properties to existing types, or creating composite types that merge different interfaces or type definitions.

type Person = {
  name: string;
  age: number;
};

type Employee = {
  employeeId: string;
  department: string;
};

type EmployeeRecord = Person & Employee;

const employee: EmployeeRecord = {
  name: "John Smith",
  age: 35,
  employeeId: "E123",
  department: "Engineering",
};

Literal Types

Literal type aliases restrict values to specific constants rather than general types. String literals create types that only accept exact string values, number literals constrain to specific numbers, and boolean literals limit to true or false. They're essential for creating precise constraints and enum-like behavior without actual enums.

type Direction = "North" | "East" | "South" | "West";

function move(direction: Direction, steps: number) {
  console.log(`Moving ${steps} steps to the ${direction}`);
}

move("North", 3); // Valid
// move("Up", 3);    // Error: Argument of type '"Up"' is not assignable to parameter of type 'Direction'

Type Aliases for Functions

Function type aliases define the shape of functions including parameter types, return types, and optional parameters. They create reusable function signatures that can be applied to variables, parameters, or properties. These aliases support overloads, generic parameters, and rest parameters, making them powerful for defining consistent API contracts and callback interfaces throughout your application.

type MathOperation = (a: number, b: number) => number;

const add: MathOperation = (a, b) => a + b;
const subtract: MathOperation = (a, b) => a - b;
const multiply: MathOperation = (a, b) => a * b;
const divide: MathOperation = (a, b) => a / b;

function performOperation(a: number, b: number, operation: MathOperation): number {
  return operation(a, b);
}

console.log(performOperation(10, 5, add)); // 15
console.log(performOperation(10, 5, subtract)); // 5

Type Aliases vs. Interfaces

While type aliases and interfaces can be used in similar ways for defining object shapes, they have some key differences.

When to Use Type Aliases

When working with union types or literal types:

Interfaces cannot represent union types directly. Type aliases are the only way to create types that accept multiple different structural possibilities, making them essential for modeling data that can have different shapes based on context or state.

type Result = Success | Error;
type HttpStatus = 200 | 201 | 400 | 404 | 500;
When you need a new name for an existing type:

Interfaces cannot constrain values to specific constants. Type aliases enable creating types that only accept exact string, number, or boolean values, which is crucial for creating precise value constraints and enum-like behavior.

type UserId = string;
When working with tuples:

Interfaces cannot represent fixed-length arrays with specific types at each position. Type aliases are required for modeling structured data like coordinates, database rows, or any scenario requiring exact array shapes with known element types.

type Point = [number, number];
When you're creating function signatures:

While interfaces can define function shapes through call signatures, type aliases provide cleaner, more direct syntax for standalone function type definitions. They're preferred for callback types, event handlers, and reusable function contracts.

type Callback = (data: string) => void;
type HTTPMethod = "GET" | "POST" | "PUT" | "DELETE";
When using primitive types:

Interfaces only work with object structures. Type aliases are necessary for creating semantic names for strings, numbers, booleans, or any non-object type, enabling better code documentation and type safety for simple values.

type Age = number;
type Email = string;
General Rule

Use interfaces for object shapes and extensible APIs. Use type aliases for everything else: unions, intersections, primitives, tuples, functions, and complex type computations that interfaces cannot express.

Real-World Examples

API Response Types

Type aliases are excellent for modeling API responses and ensuring type safety:

type User = {
  id: number;
  username: string;
  email: string;
};

type ApiResponse = {
  data: User;
  status: number;
  message: string;
  timestamp: string;
};

// Using the types
async function fetchUser(id: number): Promise<ApiResponse> {
  const response = await fetch(`/api/users/${id}`);
  return response.json();
}

// Usage
fetchUser(123).then((response) => {
  if (response.status === 200) {
    const user = response.data;
    console.log(`Found user: ${user.username}`);
  }
});

State Management

Type aliases help manage state in applications:

// Define the structure of our state
type User = {
  id: string;
  name: string;
  email: string;
};

type AuthState = {
  isAuthenticated: boolean;
  user: User | null;
  token: string | null;
  error: string | null;
};

// Initial state
const initialState: AuthState = {
  isAuthenticated: false,
  user: null,
  token: null,
  error: null,
};

// Function to update state
function login(state: AuthState, user: User, token: string): AuthState {
  return {
    ...state,
    isAuthenticated: true,
    user: user,
    token: token,
    error: null,
  };
}

// Usage
const updatedState = login(
  initialState,
  {
    id: "123",
    name: "John Doe",
    email: "john@example.com",
  },
  "auth-token-123"
);

Best Practices for Type Aliases

Use PascalCase for naming type aliases

PascalCase maintains consistency with TypeScript conventions for types, classes, and interfaces. This naming pattern immediately identifies type constructs versus variables or functions, creating visual clarity in your codebase and aligning with community standards and popular style guides.

type UserProfile = { ... }; // Good
type userProfile = { ... }; // Not ideal

Be specific with your type names

Descriptive names prevent confusion and make code self-documenting. Vague names like "Data" or "Info" provide no context about what the type represents or how it should be used. Specific names like "UserProfile" or "ApiResponse" immediately communicate purpose and domain, making code easier to understand and maintain.

// Too vague
type Data = { ... };

// More specific and clearer
type UserData = { ... };

Use type aliases for complex or reused types

Type aliases eliminate repetition and create single sources of truth for complex type definitions. When the same type structure appears multiple times, aliases ensure consistency, make updates easier, and improve code maintainability. They're particularly valuable for intricate object shapes, union types, or computed types that would be unwieldy to repeat.

// Define once, use many times
type Coordinates = {
  x: number;
  y: number;
};

function calculateDistance(point1: Coordinates, point2: Coordinates): number { ... }
function movePoint(point: Coordinates, direction: string, distance: number): Coordinates { ... }

Choose Types vs Interfaces Strategically

Interfaces excel for object shapes that might be extended, implemented by classes, or need declaration merging. Type aliases are superior for unions, intersections, primitives, tuples, and any scenario where interfaces cannot express the required type structure. The choice should reflect the type's intended usage and extensibility needs.

// Better as an interface if it might be extended
interface ApiClient {
  get(url: string): Promise<unknown>;
  post(url: string, data: unknown): Promise<unknown>;
}

// Better as a type alias
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";

Document your types with JSDoc

JSDoc comments provide essential context that type signatures alone cannot convey. They explain business rules, document valid value ranges, provide usage examples, and clarify intent for complex types. Good documentation makes types self-explanatory and reduces cognitive load when working with unfamiliar code.

/**
 * Represents a response from the API
 */
type ApiResponse = {
  /** The returned data */
  data: unknown;
  /** HTTP status code */
  status: number;
  /** Success or error message */
  message: string;
};

Exercises

Exercise 1: Basic Type Aliases

Create a few basic type aliases:

  • A type alias for a string ID

  • A type alias for a coordinate with x and y properties

  • A type alias for a configuration object with properties apiUrl (string), timeout (number), and debug (boolean)

Exercise 2: Union and Literal Types

Create a type alias for a Status that can only be one of these string literals: "pending", "processing", "completed", "failed".

Then, create a function called updateStatus that takes an ID and a Status as parameters, and returns a message confirming the update.

Exercise 3: Intersection Types

Create two separate type aliases:

  1. Person with properties name (string), age (number)

  2. Worker with properties jobTitle (string), employeeId (string)

Then, create a third type alias Employee that is an intersection of Person and Worker. Create a sample Employee object.

© 2025, SAE Academy