LearnAI ToolsCareerPractice BuildsPlayContact
Lesson 616 min read

Type Aliases & Union Types

Learn type aliases, union and intersection types, literal types, discriminated unions, and how type aliases compare to interfaces.

Introduction

Interfaces are great for describing object shapes, but not every type is an object. Sometimes a value could be one of several types, or is simply a primitive you want to give a meaningful name to. Type aliases and union types are TypeScript's tools for exactly that.

What You Will Learn
  • How to create a type alias for any type, not just object shapes.
  • How union types (`|`) let a value be one of several types.
  • How intersection types (`&`) combine multiple types into one.
  • How literal types restrict a value to a specific, exact set of values.
  • How discriminated unions model "one of several variants" data safely.

What is a Type Alias?

A type alias gives a name to any type using the `type` keyword — a primitive, an object shape, a tuple, a function, or a union of several types. Unlike an interface, a type alias can name literally any type expression.

type ID = string | number;
type Point = { x: number; y: number };
let userId: ID = 42;
let origin: Point = { x: 0, y: 0 };

Union Types

A union type, written with `|`, says a value may be one of several listed types. Before you can use a member specific to only one branch of the union, TypeScript requires you to narrow it — for example, with a `typeof` check.

function formatId(id: string | number): string {
if (typeof id === "number") {
return id.toFixed(0);
}
return id.trim();
}

Intersection Types

An intersection type, written with `&`, combines multiple types into one that must satisfy all of them simultaneously. It is the opposite of a union: instead of "one of these," it means "all of these, at once."

type Timestamped = { createdAt: Date };
type Named = { name: string };
type Record1 = Timestamped & Named;
const record: Record1 = { name: "Order #1", createdAt: new Date() };

Literal Types

A literal type narrows a primitive down to one exact, specific value. Combined with a union, literal types describe a closed, finite set of valid values — much safer than a plain `string`.

type Direction = "up" | "down" | "left" | "right";
function move(direction: Direction) {
console.log(`Moving ${direction}`);
}
move("up");
// move("diagonal"); // Error: not assignable to type 'Direction'

Interfaces vs Type Aliases

FeatureInterfaceType Alias
Can describe unions/primitives/tuplesNo — objects and function shapes onlyYes — any type expression
Declaration merging (redeclaring adds to it)YesNo — duplicate names are an error
Can use `extends`YesNo, but `&` intersections give similar results
Used with `implements` in classesYesYes, if it describes an object shape

Discriminated Unions

A discriminated union is a union of object types that all share a common literal property — the "discriminant" — which TypeScript can use to figure out exactly which variant you're working with inside a switch statement. This is one of the most useful, idiomatic patterns in everyday TypeScript.

type Circle = { kind: "circle"; radius: number };
type Square = { kind: "square"; side: number };
type Shape = Circle | Square;
function area(shape: Shape): number {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2; // shape narrowed to Circle
case "square":
return shape.side ** 2; // shape narrowed to Square
}
}
console.log(area({ kind: "square", side: 4 }));
Output

Click Run to see what this code prints.

Common Mistakes

Avoid These Mistakes
  • Trying to declaration-merge a type alias by redeclaring it — only interfaces support that; a duplicate type alias is a compile error.
  • Accessing a union-specific property before narrowing the value, which the compiler will correctly reject.
  • Writing a wide `string` or `number` type where a literal union would have caught invalid values at compile time.
  • Confusing `&` (intersection, "must satisfy all") with `|` (union, "one of these") — they are opposites.

Best Practices

  • Use type aliases for unions, primitives, tuples, and function types; use interfaces for extensible object shapes.
  • Reach for discriminated unions instead of a pile of optional properties when modeling "one of several variants" data.
  • Prefer literal unions (`"small" | "medium" | "large"`) over plain `string` whenever the valid values are a known, finite set.
  • Give discriminant properties a clear, consistent name like `kind` or `type` across all variants of a union.

Frequently Asked Questions

Most style guides recommend interfaces for public object shapes you might extend, and type aliases for everything else — unions, primitives, and function types — though both work fine for simple objects.

A union of object types that share a common literal property (the discriminant), which lets TypeScript automatically narrow which variant you have inside a switch or if statement.

No — union types are a compile-time-only construct built from literal values you already write, while enums (covered later) generate an actual object that exists at runtime.

Usually yes for a simple object shape, but you lose declaration merging and the `extends` keyword — use `&` intersections to combine type aliases instead.

Key Takeaways

  • Type aliases name any type expression, not just object shapes.
  • Union types (`|`) mean "one of these"; intersection types (`&`) mean "all of these at once."
  • Literal types restrict a value to an exact, specific set of allowed values.
  • Interfaces support declaration merging and `extends`; type aliases do not, but can express unions and primitives.
  • Discriminated unions plus a switch on a shared literal property are the idiomatic way to model variant data.

Summary

You can now model both "this object has these fields" and "this value is one of several possible types" scenarios. Next, you'll go deeper into typing functions themselves — parameters, return types, overloads, and more.

Next Lesson →

Functions in TypeScript