type

Names any type at all, including unions, tuples, mapped and conditional types.

Signature
type Name<TypeParameters> = Type;

// Type may be any of:
type Union      = A | B;
type Intersect  = A & B;
type Tuple      = [string, number, ...boolean[]];
type Mapped<T>  = { readonly [K in keyof T]?: T[K] };
type Cond<T>    = T extends Promise<infer U> ? U : T;
type Template   = `get${Capitalize<string>}`;
  • Added in TypeScript 1.4

Parameters

ParameterTypeDescription
$NameidentifierThe alias name. It must be unique in its scope — type aliases cannot merge.
$TypeParametersoptional<T extends Constraint = Default>Optional generic parameters with optional constraints and defaults. Generic type aliases arrived in TypeScript 1.6.
$Typeany type expressionThe aliased type. Unlike an interface body this is not restricted to object shapes; it may be a primitive, union, tuple, function type, template literal type, mapped type or conditional type.

Errors

Errors at compile time on a duplicate identifier if the same alias name is declared twice, and on a type alias that references itself in a way the compiler cannot resolve lazily.

Changelog

  • PHP 1.6Type aliases became generic, so `type Lazy<T> = T | (() => T)` is legal.
  • PHP 4.2Alias names started being preserved in error messages instead of always being expanded into the anonymous type they stood for, which makes errors involving aliases much easier to read.

type gives a name to a type expression. Where interface can only describe an object, an alias can name literally anything — and that is the whole reason to reach for it.

When to reach for it

  • Unions. type Status = "idle" | "loading" | "done" has no interface equivalent. Discriminated unions, the backbone of exhaustive state handling, are alias territory.
  • Tuples and function types. type Point = [number, number] or type Handler = (e: Event) => void.
  • Type-level computation. Mapped types ({ [K in keyof T]: ... }), conditional types with infer, template literal types and key remapping via as can only be written as aliases. Every built-in utility type — Partial, Record, Pick — is a type alias.
  • Renaming a primitive. type ID = string.

What trips people up

Aliases are closed: a second type User is a duplicate-identifier error, so you cannot use one to patch a library's type. Aliases are also not nominal — type UserId = string is exactly string, and any string satisfies it; branding is needed for real nominal typing. Finally, extending by intersection differs from extends: conflicting properties silently become never instead of raising an error, and deep intersection chains are measurably slower to check.

Example

Example · typescript
type Status = "idle" | "loading" | "done";
type Point = readonly [number, number];
type Result<T> = { ok: true; value: T } | { ok: false; error: string };
type Unwrap<T> = T extends Promise<infer U> ? U : T;

const s: Status = "done";
const p: Point = [3, 4];
const r: Result<number> = { ok: true, value: 42 };
const u: Unwrap<Promise<string>> = "unwrapped";

console.log(s, p.join(","), r.ok && r.value, u);
// done 3,4 42 unwrapped

When to use it

  • Model a component's state as a union of string literals so switch statements can be checked for exhaustiveness.
  • Name a discriminated union for an API result that is either a success payload or an error.
  • Write a reusable generic helper that transforms every key of another type.
  • Give a readable name to a long inline function or tuple type used in several signatures.

Frequently asked questions

When should I use type instead of interface?

Use type whenever the thing you are naming is not a plain object shape — unions, tuples, function types, primitives, mapped types, conditional types and template literal types all require an alias. For ordinary object shapes either works, and the handbook suggests defaulting to interface until you need a type feature.

Can you extend a type alias in TypeScript?

Yes, by intersecting it: `type Bear = Animal & { honey: boolean }`. Be aware this behaves differently from interface extends — if both sides declare the same property with incompatible types, the result is silently `never` instead of a compile error, and long intersection chains are slower for the compiler than extends.

Why can't I declare the same type alias twice?

Type aliases do not participate in declaration merging, so a repeated name is a duplicate-identifier error. If you need to add members to a type that already exists — especially one from a library — you must use an interface, since only interfaces can be reopened.

More examples

A discriminated union that narrows exhaustively

The kind property discriminates the union, so inside each case TypeScript knows exactly which member it holds and permits radius or side accordingly. The never assignment in the default branch is the exhaustiveness check: add a third member to Shape and that line stops compiling, which is why this pattern is preferred over a chain of if statements.

Example · typescript
type Shape =
  | { kind: "circle"; radius: number }
  | { kind: "square"; side: number };

function area(shape: Shape): number {
  switch (shape.kind) {
    case "circle":
      return Math.PI * shape.radius ** 2;   // narrowed to the circle member
    case "square":
      return shape.side ** 2;
    default: {
      const exhaustive: never = shape;      // errors if a member is added
      return exhaustive;
    }
  }
}

console.log(area({ kind: "circle", radius: 2 }).toFixed(2));
console.log(area({ kind: "square", side: 3 }));
// 12.57
// 9

Mapped and conditional types with key remapping

Getters<Person> remaps each key through a template literal type, producing getName and getAge, and the values become zero-argument functions returning the original property types. Mutable shows the modifier prefixes: -readonly strips readonly and -? strips optionality. None of this is expressible with an interface, which is the clearest signal that you need a type alias.

Example · typescript
interface Person {
  name: string;
  age: number;
}

type Getters<T> = {
  [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};

type Mutable<T> = { -readonly [K in keyof T]-?: T[K] };

const person: Person = { name: "Ada", age: 36 };
const api: Getters<Person> = {
  getName: () => person.name,
  getAge: () => person.age,
};

console.log(api.getName(), api.getAge());
console.log(Object.keys(api).sort().join(","));
// Ada 36
// getAge,getName

Discussion

  • Be the first to comment on this lesson.