TypeScript Type Aliases — Complete Guide
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
typekeyword and its syntax - Union types (
|) and intersection types (&) - Literal types for exact values
- Discriminated unions basics
- When to use
typevsinterface
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
What is the difference between
string | numberandstring & number? Union means either. Intersection of primitives is impossible (never).Can you create a type alias for a tuple? Yes:
type Pair = [string, number];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.
Can a
typealias 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
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