Skip to content

TypeScript Type Aliases — Complete Guide

DodaTech Updated 2026-06-28 8 min read

In this tutorial, you will learn about TypeScript Type Aliases. We cover key concepts, practical examples, and best practices to help you master this topic.

TypeScript type aliases give names to any type — not just object shapes — enabling you to create unions, intersections, literal types, and complex composed types that make your code more expressive and safer.

What You'll Learn

  • The type keyword and its syntax
  • Union types (|) and intersection types (&)
  • Literal types for exact values
  • Discriminated unions basics
  • When to use type vs interface

Why It Matters

While interfaces are great for object shapes, TypeScript's type system is structural (nominal in simulation via brands). Type aliases unlock the full power of that structural system — you can Express "one of these values", "this and that combined", or "any value matching this pattern" with simple, readable syntax.

Real-World Use

The Durga Antivirus Pro threat detection engine uses union types for threat categories ("malware" | "ransomware" | "phishing" | "adware"), intersection types for combining base entity fields with specific threat data, and literal types for API version strings. These types prevent entire classes of bugs where a threat category string is misspelled or mismatched.

Learning Path

flowchart LR
  A[Interfaces] --> B[Type Aliases]
  B --> C[Functions]
  B --> D[You Are Here]
  C --> E[Enums]
  E --> F[Type Assertions]
  F --> G[Generics Basics]

The type Keyword

type UserID = string;
type Age = number;
type Callback = (result: string) => void;

const id: UserID = "usr-001";
const cb: Callback = (result) => console.log(result);

Think of type as a nickname for any type — it never creates a new type, just an alias.

Union Types (|)

A value that can be one of several types:

type Status = "active" | "inactive" | "pending";
type ID = string | number;
type Result = { success: true; data: string } | { success: false; error: string };

const status: Status = "active";
const userId: ID = "abc123"; // Could also be a number

Union with Primitives

type InputValue = string | number | boolean;

function process(value: InputValue): void {
  if (typeof value === "string") {
    console.log(value.toUpperCase());
  } else if (typeof value === "number") {
    console.log(value.toFixed(2));
  } else {
    console.log(value ? "True" : "False");
  }
}

process("hello"); // HELLO
process(42.123);  // 42.12
process(true);    // True

Why this matters: Without narrowing, you can only access properties common to all types in the union. TypeScript's control flow analysis (covered in lesson 26) helps you narrow within each branch.

Discriminated Unions

A union where each member has a common property (the "discriminant") that TypeScript can use to narrow:

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

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

console.log(area({ kind: "circle", radius: 5 }));        // 78.54
console.log(area({ kind: "rectangle", width: 4, height: 5 })); // 20

Intersection Types (&)

Combine multiple types into one:

type Identifiable = { id: string };
type Timestamped = { createdAt: Date; updatedAt: Date };

type Entity = Identifiable & Timestamped;

const entity: Entity = {
  id: "ent-001",
  createdAt: new Date("2024-01-01"),
  updatedAt: new Date("2024-06-01"),
};

Intersection with Interfaces

interface HasName { name: string; }
interface HasAge { age: number; }

type Person = HasName & HasAge;

const alice: Person = { name: "Alice", age: 30 };

Conflicting Intersections

type A = { x: number };
type B = { x: string };
type C = A & B;
// x is type: never (number & string is impossible)

Literal Types

Literal types specify exact values:

type Direction = "north" | "south" | "east" | "west";
type HTTPMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
type Port = 3000 | 8080 | 443;
type Truthy = true;

function navigate(dir: Direction): void {
  console.log(`Moving ${dir}`);
}

navigate("north"); // OK
// navigate("up"); // Error: '"up"' is not assignable to type 'Direction'

Template Literal Types (TS 4.1+)

type EventName = `on${Capitalize<string>}`;
type CSSKey = `--${string}`;

// type Color = "red" | "green" | "blue";
type DarkColor = `dark-${Color}`; // "dark-red" | "dark-green" | "dark-blue"

Type Aliases with Functions

type MathOperation = (a: number, b: number) => number;

const add: MathOperation = (a, b) => a + b;
const multiply: MathOperation = (a, b) => a * b;

console.log(add(5, 3));      // 8
console.log(multiply(5, 3)); // 15

Type vs Interface: When to Use Which

Scenario Use Reason
Object shape that could be extended interface Declaration merging, better errors
Union or intersection type Interface can't express unions
Tuple type type Pair = [string, number]
Primitive alias type type UserID = string
Public API / library interface Consumers can extend via declaration merging
Complex computed type type type Keys = keyof T

Common Mistakes

1. Using type When interface Would Be Better

// Works but prevents declaration merging
type User = { name: string; age: number };

// Better as interface — can be extended later
interface User { name: string; age: number; }

2. Confusing Union (|) and Intersection (&)

type A = { name: string };
type B = { age: number };

type Union = A | B;       // Either name OR age (or both)
type Intersection = A & B; // MUST have both name AND age

3. Overly Complex One-Liner Types

// Hard to read
type Complex = { data: Record<string, { value: string | number | boolean; meta?: { tags: string[] } }> };

// Better with intermediate types
type MetaData = { tags: string[] };
type DataEntry = { value: string | number | boolean; meta?: MetaData };
type Complex = { data: Record<string, DataEntry> };

4. Forgetting That Types Are Not Runtime Values

type Color = "red" | "green" | "blue";
// if (color instanceof Color) {} // Error: 'Color' only refers to a type

You need runtime narrowing: if (color === "red" || color === "green").

5. Intersection Conflicting the Same Property

type A = { id: string };
type B = { id: number };
type C = A & B; // id: string & number → never — logical impossible

6. Not Using Discriminated Unions When Appropriate

Instead of checking types at multiple places, use a single discriminant field to narrow in one step.

Practice Questions

  1. What is the difference between string | number and string & number? Union means either. Intersection of primitives is impossible (never).

  2. Can you create a type alias for a tuple? Yes: type Pair = [string, number];

  3. What is a discriminated union? A union type where each member has a common property (the discriminant) with a literal type, enabling TypeScript to narrow the type based on that property.

  4. Can a type alias reference itself? Yes, for recursive types like JSON: type JSONValue = string | number | boolean | null | JSONValue[] | { [key: string]: JSONValue };

Challenge: Define types for a payment processing system. Create a discriminated union PaymentMethod with credit card, PayPal, and bank transfer variants. Write a function that calculates a processing fee for each method.

FAQ

Can I extend a type alias?

Not directly. Use intersection (&) instead: type B = A & { extra: string };. Interfaces use extends.

Can a type alias be used in declaration merging?

No. Only interfaces support declaration merging. If you define the same type twice, you get a duplicate identifier error.

What is the `never` type in unions?

never represents an impossible state. In unions, it gets absorbed: "a" | never"a". Useful for filtering types.

Can I use computed properties in a type alias?

Yes. type Keys = "a" | "b"; type Obj = { [K in Keys]: number }; creates { a: number; b: number }.

What is the difference between `type` and `class`?

type is a compile-time construct (erased). class is both a type (instance shape) and a runtime value (constructor function).

Mini Project: Shape Calculator with Union Types

// src/shape-calc.ts

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

function area(shape: Shape): number {
  switch (shape.kind) {
    case "circle":
      return Math.PI * shape.radius ** 2;
    case "square":
      return shape.side ** 2;
    case "rectangle":
      return shape.width * shape.height;
  }
}

function perimeter(shape: Shape): number {
  switch (shape.kind) {
    case "circle":
      return 2 * Math.PI * shape.radius;
    case "square":
      return 4 * shape.side;
    case "rectangle":
      return 2 * (shape.width + shape.height);
  }
}

const shapes: Shape[] = [
  { kind: "circle", radius: 5 },
  { kind: "square", side: 4 },
  { kind: "rectangle", width: 3, height: 7 },
];

for (const shape of shapes) {
  console.log(`${shape.kind}: area=${area(shape).toFixed(2)}, perimeter=${perimeter(shape).toFixed(2)}`);
}

Expected output:

circle: area=78.54, perimeter=31.42
square: area=16.00, perimeter=16.00
rectangle: area=21.00, perimeter=20.00

What's Next

Now learn how to apply these types to functions with full type safety:

Lesson Description
{{< ref "/programming-languages/typescript/04-interfaces" >}} Review interfaces
{{< ref "/programming-languages/typescript/06-functions" >}} Parameter types, return types, function overloads
{{< ref "/programming-languages/typescript/07-enums" >}} Numeric and string enums

Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro