Type Aliases
A type alias gives a name to any type — not just object shapes, but unions, intersections, primitives, tuples, functions, and the results of mapped or conditional types. You declare one with the type keyword, and from then on the alias is interchangeable with the type it names. Aliases do not create new types at runtime; they are erased during compilation and exist purely to make your code more readable and reusable. This page covers how to declare aliases, make them generic, and decide when to reach for a type alias versus an interface.
Declaring a type alias
Use type Name = Definition. The alias can stand in anywhere the underlying type is valid, and the compiler treats the two as structurally identical.
type UserId = string;
type Point = { x: number; y: number };
type Coordinates = [number, number];
const id: UserId = "u_8f2a";
const origin: Point = { x: 0, y: 0 };
const cell: Coordinates = [3, 7];
Aliasing a primitive like string does not produce a distinct nominal type — UserId and string are mutually assignable. The value is documentation and a single place to change the definition, not type-level safety against mixing the two.
Aliasing unions, functions, and tuples
The real power of type is naming things an interface cannot express. Unions, function signatures, and tuples all become reusable with a single declaration.
type Status = "idle" | "loading" | "success" | "error";
type Handler = (event: string, payload: unknown) => void;
type RGB = readonly [r: number, g: number, b: number];
const onMessage: Handler = (event, payload) => console.log(event, payload);
const red: RGB = [255, 0, 0];
Naming the union Status once means every function that accepts a status shares the exact same set of allowed strings, and widening the set later is a one-line change. The labeled tuple elements (r, g, b) show up in editor tooltips and improve readability.
Generic type aliases
Aliases accept type parameters, which lets you build reusable type constructors. This is the idiomatic way to model wrappers like API envelopes or result types.
type ApiResponse<T> = {
data: T;
status: number;
error: string | null;
};
type Result<T, E = Error> =
| { ok: true; value: T }
| { ok: false; error: E };
const userResponse: ApiResponse<{ name: string }> = {
data: { name: "Ada" },
status: 200,
error: null,
};
Default type parameters (E = Error) make a parameter optional at the use site. Generic aliases compose freely: ApiResponse<Result<User>> is perfectly valid and fully type-checked.
Aliases reference, not copy
A type alias is a name pointing at a type, so the compiler reports errors using the original structure, not the alias name. Aliases also support recursion, which is essential for tree-like and JSON data.
type Json =
| string
| number
| boolean
| null
| Json[]
| { [key: string]: Json };
const config: Json = {
name: "app",
version: 2,
features: ["a", "b"],
nested: { enabled: true },
};
A
typealias cannot be redeclared or merged. Declaring the sametypename twice is a “Duplicate identifier” error — unlikeinterface, which silently merges. Reach for an interface only when you actually want that merging behavior.
type vs interface
Both name object shapes, and for plain objects they are nearly interchangeable. The decision comes down to which capabilities you need.
| Capability | type alias | interface |
|---|---|---|
| Object shapes | Yes | Yes |
| Unions / intersections | Yes | No |
| Mapped & conditional types | Yes | No |
| Tuples & primitives | Yes | No |
extends another type | Via & | Yes (extends) |
| Declaration merging | No | Yes |
| Show as alias in errors | Often expanded | Keeps the name |
In short: prefer interface for public, extendable object contracts that may be augmented; reach for type whenever you need a union, a tuple, a function signature, or any computed type. Many teams default to type for its consistency and only switch to interface when they specifically need merging or extends.
Best Practices
- Use
typefor unions, tuples, functions, and computed types — things interfaces cannot express. - Prefer
interfacefor public object contracts that consumers may extend or merge. - Make aliases generic to build reusable wrappers like
Result<T>orApiResponse<T>. - Give defaults to type parameters when a sensible fallback exists (
<T, E = Error>). - Remember aliases are erased at runtime and do not create nominal types.
- Pick one of
type/interfaceas your team default and switch only when the other’s features are required. - Use recursive aliases (like a
Jsontype) instead of resorting toanyfor self-referential data.