TypeScriptType Aliases

Type Aliases

A type alias gives a name to any type expression. Once named, you can reuse that name wherever the full type would otherwise appear — making complex types readable, composable, and maintainable.

Type aliases are one of the most fundamental tools in TypeScript. You will use them everywhere.

The type Keyword

The syntax is simple: type Name = TypeExpression. The alias can be used anywhere a type is expected.

TS
// Aliasing a primitive
type UserId    = string;
type Age       = number;
type IsActive  = boolean;

// Aliasing an object shape
type Point = {
  x: number;
  y: number;
};

// Aliasing a union
type StringOrNumber = string | number;

// Aliasing a function signature
type Predicate<T> = (value: T) => boolean;

// Using the aliases
const id: UserId = 'user-42';
const pos: Point = { x: 10, y: 20 };
const isEven: Predicate<number> = (n) => n % 2 === 0;
Note
Type aliases are purely a compile-time construct. At runtime there is no trace of them — they are erased during compilation just like all TypeScript types.
Aliasing Primitives

Aliasing a primitive (like string) does not create a new distinct type — it is still structurally identical to the base type. But it communicates intent and makes refactoring easier.

TS
type Email    = string;
type Password = string;

// Both are still string under the hood, so TypeScript allows this
function login(email: Email, password: Password) { /* ... */ }

const e: Email    = 'alice@example.com';
const p: Password = 'hunter2';

// These are interchangeable at the type level — see Branded Types for true opaque aliases
login(p, e); // TypeScript does NOT catch this argument swap — both are string
Warning
Plain primitive aliases are documentation only — they do not prevent mixing up arguments of the same underlying type. If you need truly distinct types (e.g. UserId that cannot be accidentally used as an OrderId), look into branded/nominal types.
Aliasing Object Types

Object type aliases are one of the most common uses. They let you name a shape and reuse it across your codebase without repetition.

TS
type Address = {
  street: string;
  city:   string;
  zip:    string;
  country: string;
};

type User = {
  id:       string;
  name:     string;
  email:    string;
  address:  Address; // reusing the alias
};

type Order = {
  id:              string;
  userId:          string;
  shippingAddress: Address; // reuse again
  totalCents:      number;
};
Aliasing Unions and Intersections

Type aliases make complex union and intersection types readable by giving them a meaningful name.

TS
// Named union — much cleaner than repeating the union everywhere
type Status = 'pending' | 'active' | 'suspended' | 'deleted';

// Named intersection — combines two shapes under a descriptive name
type Timestamped = { createdAt: Date; updatedAt: Date };
type WithId      = { id: string };

type BaseEntity = WithId & Timestamped;

type Product = BaseEntity & {
  name:  string;
  price: number;
};

// The full type is clear without expanding everything
function updateProduct(product: Product) { /* ... */ }
Aliasing Function Types

Function type aliases make callback signatures readable and reusable. Without aliases, long function type annotations clutter code and are difficult to update.

TS
// Without alias — repeated, hard to read
function on(event: string, handler: (event: MouseEvent) => void) {}

// With alias — reusable, self-documenting
type MouseHandler = (event: MouseEvent) => void;
type KeyHandler   = (event: KeyboardEvent) => void;
type Comparator<T> = (a: T, b: T) => number;

function on(event: string, handler: MouseHandler) {}

function sort<T>(items: T[], compare: Comparator<T>): T[] {
  return [...items].sort(compare);
}

const byAge: Comparator<{ age: number }> = (a, b) => a.age - b.age;
Tip
When a function signature appears more than once in your codebase, give it a type alias. It becomes much easier to update the signature in one place if requirements change.
Type Aliases vs Interfaces

Both type and interface can describe object shapes, but they have key differences. There is a dedicated page on this topic — here is a practical summary.

Feature

type alias

interface

Object shapes

Yes

Yes

Union types

Yes — type A = X | Y

No

Intersection / extends

Yes — via &

Yes — via extends

Primitives & tuples

Yes

No

Declaration merging

No

Yes (can be re-opened)

Generic parameters

Yes

Yes

Recursive definitions

Yes (with some caveats)

Yes

Error messages

Shows the alias name

Shows the interface name

Note
A common guideline: use interface for public API surfaces and class contracts; use type for everything else (unions, intersections, function types, utility types). Both are valid — consistency within a codebase matters more than the choice itself.
Recursive Type Aliases

Type aliases can refer to themselves, enabling recursive types. The classic example is a JSON value type.

TS
// JSON value: any value that can appear in valid JSON
type JsonValue =
  | string
  | number
  | boolean
  | null
  | JsonValue[]
  | { [key: string]: JsonValue };

const config: JsonValue = {
  name: 'app',
  version: 1,
  flags: { dark: true },
  tags: ['ts', 'node'],
};

// Tree node
type TreeNode<T> = {
  value: T;
  left:  TreeNode<T> | null;
  right: TreeNode<T> | null;
};

const tree: TreeNode<number> = {
  value: 10,
  left:  { value: 5,  left: null, right: null },
  right: { value: 15, left: null, right: null },
};
Success
Recursive type aliases are one area where type is clearly superior tointerface — the self-referential union pattern (JsonValue) is concise and expressive.
Generic Type Aliases

Type aliases accept generic parameters, making them powerful building blocks for utility types.

TS
// A result type that is either a success or an error
type Result<T, E = Error> =
  | { ok: true;  value: T }
  | { ok: false; error: E };

function divide(a: number, b: number): Result<number, string> {
  if (b === 0) return { ok: false, error: 'Division by zero' };
  return { ok: true, value: a / b };
}

const r = divide(10, 2);
if (r.ok) {
  console.log(r.value); // TypeScript knows this is number
} else {
  console.error(r.error); // TypeScript knows this is string
}

// Generic wrapper for paginated data
type Paginated<T> = {
  data:       T[];
  page:       number;
  totalPages: number;
};

type UserPage  = Paginated<User>;
type OrderPage = Paginated<Order>;
Practical Example: API Response Wrapper

A real-world pattern that ties together unions, generics, and type aliases: a typed wrapper for all API responses across your application.

TS
type ApiError = {
  code:    string;
  message: string;
  details?: unknown;
};

type ApiResponse<T> =
  | { status: 'success'; data: T }
  | { status: 'error';   error: ApiError }
  | { status: 'loading' };

// Reusable across every endpoint
type UserResponse    = ApiResponse<User>;
type ProductResponse = ApiResponse<Product>;
type ListResponse<T> = ApiResponse<Paginated<T>>;

function renderUserProfile(res: UserResponse) {
  switch (res.status) {
    case 'loading': return <Spinner />;
    case 'error':   return <ErrorBanner message={res.error.message} />;
    case 'success': return <Profile user={res.data} />;
  }
}
Event Handler Type

Another practical pattern: aliasing event handler function types so React component props stay concise.

TS
import { ChangeEvent, FormEvent } from 'react';

type InputHandler   = (e: ChangeEvent<HTMLInputElement>)  => void;
type SelectHandler  = (e: ChangeEvent<HTMLSelectElement>) => void;
type SubmitHandler  = (e: FormEvent<HTMLFormElement>)     => void;

interface LoginFormProps {
  onEmailChange:    InputHandler;
  onPasswordChange: InputHandler;
  onSubmit:         SubmitHandler;
}

// Much cleaner than repeating the full event type every time
function LoginForm({ onEmailChange, onPasswordChange, onSubmit }: LoginFormProps) {
  return (
    <form onSubmit={onSubmit}>
      <input type="email"    onChange={onEmailChange} />
      <input type="password" onChange={onPasswordChange} />
      <button type="submit">Login</button>
    </form>
  );
}
Making Complex Types Readable

One of the most underrated uses of type aliases is simply naming intermediate types to break up deeply nested or long type expressions.

TS
// Hard to read — everything inline
function process(
  data: Record<string, { items: Array<{ id: string; value: number }> }>
): Map<string, number[]> { /* ... */ }

// Easy to read — named intermediate types
type ItemEntry   = { id: string; value: number };
type GroupedData = Record<string, { items: ItemEntry[] }>;
type ValueMap    = Map<string, number[]>;

function process(data: GroupedData): ValueMap { /* ... */ }
When to Reach for a Type Alias
  1. Any union type — type Status = "active" | "inactive"

  2. Any intersection type — type AdminUser = User & AdminRole

  3. Function signatures used more than once — type Handler = (e: Event) => void

  4. Recursive types like JSON or tree structures

  5. Generic utility wrappers like Result<T>, Paginated<T>, ApiResponse<T>

  6. Simplifying deeply nested inline type expressions

  7. When you want a name for documentation or self-description purposes

Quick Reference
  • type Name = TypeExpression — creates a reusable alias

  • Can alias primitives, objects, unions, intersections, functions, tuples, generics

  • Unlike interfaces, type aliases cannot be re-opened via declaration merging

  • Recursive aliases are supported and are great for JSON, tree, and linked-list types

  • Generic aliases (type Result<T>) are the foundation of TypeScript utility types

  • Type aliases are erased at runtime — zero performance cost