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.
// 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;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.
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 stringUserId 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.
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.
// 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.
// 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;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 — | No |
Intersection / extends | Yes — via | Yes — via |
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 |
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.
// 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 },
};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.
// 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.
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.
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.
// 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
Any union type —
type Status = "active" | "inactive"Any intersection type —
type AdminUser = User & AdminRoleFunction signatures used more than once —
type Handler = (e: Event) => voidRecursive types like JSON or tree structures
Generic utility wrappers like
Result<T>,Paginated<T>,ApiResponse<T>Simplifying deeply nested inline type expressions
When you want a name for documentation or self-description purposes
Quick Reference
type Name = TypeExpression— creates a reusable aliasCan 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 typesType aliases are erased at runtime — zero performance cost