Type Aliases

Difficulty: Intermediate

Type aliases create a name for any type using the `type` keyword. While interfaces can only describe object shapes, type aliases can name any type: primitives, unions, intersections, tuples, functions, and more. The syntax is `type MyType = ...`. Type aliases are one of TypeScript's most versatile features and are essential for expressing complex type relationships.

Union types, written with the `|` operator, represent a value that could be one of several types: `type Result = string | number`. A variable of a union type can hold any of the constituent types. To use type-specific operations, you must narrow the type first using type guards like `typeof`, `instanceof`, or property checks. Unions are everywhere in TypeScript - optional properties are unions with `undefined`, nullable values are unions with `null`, and API responses often use union types to represent different outcomes.

Intersection types, written with the `&` operator, combine multiple types into one. `type Admin = User & { permissions: string[] }` creates a type that has all properties of `User` plus a `permissions` array. Intersections are the type-level equivalent of "and" - the resulting type must satisfy all constituent types. They're commonly used to add properties to existing types, combine mixins, and build complex types from simpler ones.

Discriminated unions are a powerful pattern that combines union types with a shared literal property (the discriminant). Each member of the union has a common property with a different literal value: `type Shape = { kind: "circle"; radius: number } | { kind: "square"; side: number }`. By checking the discriminant (`shape.kind`), TypeScript narrows the type to the specific member, enabling safe access to member-specific properties. This pattern is the TypeScript way to implement tagged unions or algebraic data types.

Code examples

Union types and type narrowing

type StringOrNumber = string | number;
type Result = { success: true; data: string } | { success: false; error: string };

function formatValue(value: StringOrNumber): string {
  if (typeof value === "string") {
    return value.toUpperCase();
  }
  return value.toFixed(2);
}

function handleResult(result: Result): string {
  if (result.success) {
    return `Data: ${result.data}`;
  }
  return `Error: ${result.error}`;
}

console.log(formatValue("hello"));
console.log(formatValue(3.14));
console.log(handleResult({ success: true, data: "loaded" }));
console.log(handleResult({ success: false, error: "timeout" }));

Union types are narrowed using type guards. `typeof` narrows primitives, while checking a boolean property narrows object unions. After narrowing, TypeScript knows the exact type.

Intersection types

type HasName = { name: string };
type HasEmail = { email: string };
type HasAge = { age: number };

// Intersection combines all properties
type Person = HasName & HasEmail & HasAge;

function introduce(person: Person): string {
  return `${person.name} (${person.age}) - ${person.email}`;
}

// Adding permissions to an existing type
type Admin = Person & { role: "admin"; permissions: string[] };

const admin: Admin = {
  name: "Alice",
  email: "alice@company.com",
  age: 30,
  role: "admin",
  permissions: ["read", "write", "delete"]
};

console.log(introduce(admin));
console.log(`Role: ${admin.role}, Perms: ${admin.permissions.join(", ")}`);

Intersections combine types with `&`. A `Person` has all properties from HasName, HasEmail, and HasAge. `Admin` extends Person with additional properties.

Discriminated unions

type Shape =
  | { kind: "circle"; radius: number }
  | { kind: "rectangle"; width: number; height: number }
  | { kind: "triangle"; base: number; height: number };

function getArea(shape: Shape): number {
  switch (shape.kind) {
    case "circle":
      return Math.PI * shape.radius  2;
    case "rectangle":
      return shape.width * shape.height;
    case "triangle":
      return 0.5 * shape.base * shape.height;
  }
}

console.log(getArea({ kind: "circle", radius: 5 }).toFixed(2));
console.log(getArea({ kind: "rectangle", width: 4, height: 6 }));
console.log(getArea({ kind: "triangle", base: 3, height: 8 }));

The `kind` property is the discriminant. When you check `shape.kind === 'circle'`, TypeScript narrows the type and knows `shape.radius` exists. This pattern provides exhaustive type-safe handling.

Key points

Concepts covered

type keyword, Union types, Intersection types, Discriminated unions