Type Aliases: Naming Unions, Intersections, Functions, and Generics

Learn to use type to create reusable, readable names for any kind of type — object shapes, unions, intersections, function signatures, generics, and self-referential structures.

A type alias is simply a name you give to a type so you can reuse it, document intent, and avoid repeating a complex type expression across your codebase.

Declaring and reusing type aliases

The type keyword binds any type expression to a name: type ID = string | number; creates a new name, ID, that can be used anywhere a type is expected, exactly as if you had written string | number directly. Unlike interface, which only names object shapes, type can alias literally any type — primitives, unions, intersections, tuples, function signatures, and more.

Aliasing an object shape with type User = { id: number; name: string }; behaves identically, for the purposes of structural type checking, to declaring the same shape with interface User { id: number; name: string } — an object satisfies either one if it has the right properties. The choice between them, covered in depth in the Interfaces module, mostly comes down to whether you need interface-only features like declaration merging.

The main value of a type alias is DRY (don't repeat yourself) type definitions: once ID or User is declared, you write it wherever that concept is needed instead of retyping the full definition, and if the underlying shape changes, you update it in exactly one place. This also documents intent for readers — function findUser(id: ID) communicates more about what the parameter represents than function findUser(id: string | number) does, even though they're equivalent to the compiler.

Union and intersection aliases

A union alias names a value that can be one of several types: type ID = string | number means a value of type ID is either a string or a number, and nothing else. Unions are TypeScript's primary tool for modeling "one of these possibilities," and naming them keeps call signatures and object properties readable instead of repeating long |-separated lists everywhere they're used.

An intersection alias combines multiple types into one that must satisfy all of them simultaneously: type Employee = Person & Developer produces a type whose values must have every property from both Person and Developer. Intersections are most useful for composing smaller, focused object shapes into a larger one, and are covered in full depth, including their pitfalls, in the Union and Intersection Types module.

Union and intersection aliases can themselves be built from other aliases, letting you assemble a vocabulary of small, named building blocks — type Loading = { status: "loading" }, type Loaded = { status: "loaded"; data: string }, and then type FetchState = Loading | Loaded — that reads almost like a specification of your application's states.

Function type aliases and generic aliases

A type alias can name a function signature, which is invaluable for callback parameters and any place the same function shape is used repeatedly: type Comparator<T> = (a: T, b: T) => number; names the signature that Array.prototype.sort expects, and any function matching that shape — same parameter types, compatible return type — can be assigned to a variable of type Comparator<T>.

Type aliases can also be generic, taking type parameters just like functions take value parameters: type Pair<A, B> = { first: A; second: B }; lets you produce a specific pair type on demand, such as Pair<string, number>, without writing a new alias for every combination of types you need. Generic aliases are how most reusable, library-style types (results, options, containers) are expressed in TypeScript.

Combining these two ideas, a generic function type alias like type AsyncFetcher<T> = () => Promise<T>; names an entire family of function shapes at once — AsyncFetcher<User>, AsyncFetcher<Product[]>, and so on — all sharing the same structural pattern (a zero-argument function returning a promise) while remaining precise about what each one actually resolves to.

Recursive type aliases

A type alias can refer to itself in its own definition, which is essential for modeling naturally recursive data like trees, nested comment threads, or JSON: type TreeNode = { value: number; children: TreeNode[] }; says a TreeNode holds a number and an array of more TreeNodes, and TypeScript is able to resolve this self-reference as long as it's not immediately circular (the recursive reference must appear inside a structure like an array, object property, or function return, not as the alias's entire definition).

Recursive aliases are how TypeScript types deeply nested, self-similar data without knowing its depth ahead of time — a JSONValue alias, for example, is commonly defined as type JSONValue = string | number | boolean | null | JSONValue[] | { [key: string]: JSONValue };, capturing that JSON can nest arrays and objects containing more JSON to arbitrary depth.

One practical limit worth knowing is that TypeScript restricts how "instantiation-heavy" a recursive generic alias can get before it gives up with an error about excessively deep or infinite type instantiation; this mostly comes up with generic recursive aliases used at very deep, statically-known nesting levels, and is rarely a concern for everyday tree- or JSON-shaped data.

Type alias forms

ID is a union alias usable anywhere either a string or number ID is accepted. Comparator<T> names a function shape generically, so it can describe a comparator for any element type. TreeNode<T> refers to itself inside the children array, which is what makes it recursive.

type ID = string | number;

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

type Comparator<T> = (a: T, b: T) => number;

type TreeNode<T> = {
  value: T;
  children: TreeNode<T>[];
};

Code Examples

ID names the union string | number, so printId accepts either kind of identifier. Calling it with either a number or a string compiles, but calling it with a boolean would not. Expected result: Requested ID: 101 Requested ID: e29b-41d4

Comparator<string> names the exact function shape Array.prototype.sort expects for an array of strings: two string parameters and a number return value. Declaring byLength with that alias documents its purpose and catches a mismatched signature at the declaration site rather than deep inside sort. Expected result: [ 'fig', 'kiwi', 'banana' ]

type ID = string | number;

function printId(id: ID): void {
  console.log(`Requested ID: ${id}`);
}

printId(101);
printId("e29b-41d4");
type Comparator<T> = (a: T, b: T) => number;

const byLength: Comparator<string> = (a, b) => a.length - b.length;

const words = ["banana", "fig", "kiwi"];
words.sort(byLength);
console.log(words);

Important Rules

Use a type alias whenever a type expression — especially a union, intersection, or function signature — is repeated more than once, or is complex enough that naming it improves readability.

A recursive alias must nest its self-reference inside an object property, array, or function signature; an alias cannot be defined as directly equal to itself.

Generic aliases should be parameterized on the types that actually vary between uses — don't add a type parameter for something that's always the same concrete type in practice.

For plain object shapes, type and interface are largely interchangeable; reach for type by default when you also need unions, intersections, or aliasing of non-object types.

Where this shows up

Type aliases are the backbone of most TypeScript API surfaces: union aliases model status codes and discriminated states, function type aliases document callback and event-handler shapes, generic aliases power reusable result and container types (like a Result<T, E> success/error type), and recursive aliases type JSON payloads, file-system trees, and nested UI component trees.

type Broken = Broken; // directly self-referential with no wrapping structure type TreeNode = { value: number; children: TreeNode[] };

A type alias cannot be defined as directly and immediately equal to itself — the recursive reference must be nested inside another structure (an object property, array, or function type) for TypeScript to resolve it.

[multiple-choice] Which of the following can a type alias name that an interface cannot? A. An object shape with required properties B. A union type like string | number C. A property that is optional D. A property that is readonly

B. A union type like string | number — interface can only describe object (and function/callable) shapes. type can alias any type expression, including unions, intersections, primitives, and tuples, which interface cannot.

[true-false] A recursive type alias can be written as type Broken = Broken; and TypeScript will resolve it. A. True B. False

B. False — A directly self-referential alias with no wrapping structure is circular and TypeScript rejects it. The self-reference must be nested inside an object property, array, or function signature to be resolvable.

[multiple-choice] What is the primary purpose of a generic type alias like type Pair<A, B> = { first: A; second: B }? A. To force A and B to always be the same type B. To produce a family of related types on demand without redefining the shape for every combination C. To make the properties optional D. To convert the type into a runtime function

B. To produce a family of related types on demand without redefining the shape for every combination — Generic type aliases parameterize a shape so it can be instantiated for many concrete type combinations (Pair<string, number>, Pair<User, Product>, etc.) from a single reusable definition.

Declare a generic type alias Result<T> that is a union of { ok: true; value: T } and { ok: false; error: string }. Write a function divide(a: number, b: number): Result<number> that returns the error variant when dividing by zero and the success variant otherwise.

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

Give each branch of the union a distinct literal value for ok (true vs false) so callers can narrow which branch they have.