Unions and intersections are how you compose types together. They are the backbone of flexible, type-safe APIs.
Union Types (OR)
A value can be one of several types:
type Status = "loading" | "success" | "error";
type ID = string | number;
type Nullish = null | undefined;Union types are especially powerful with string literals:
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";
function request(method: HttpMethod, url: string) {
console.log(`${method} ${url}`);
}
request("GET", "/api/users"); // ✅
request("PATCH", "/api/users"); // ❌ Error: not a valid methodDiscriminated Unions
The most powerful pattern in TypeScript. Add a common type (or kind)
field to each variant:
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 0.5 * shape.base * shape.height;
}
}TypeScript narrows the type inside each case — you only have
access to the properties of that specific variant.
Intersection Types (AND)
Combine multiple types with & — the result has all properties:
type HasName = { name: string };
type HasAge = { age: number };
type HasEmail = { email: string };
type Person = HasName & HasAge & HasEmail;
const person: Person = {
name: "Sita",
age: 21,
email: "sita@example.com",
};Union vs Intersection
| Operator | Meaning | Result |
|---|---|---|
| (union) |
A or B | Has properties of A or B |
& (intersection) |
A and B | Has properties of A and B |
type A = { x: number };
type B = { y: string };
type Union = A | B; // { x: number } OR { y: string }
type Intersection = A & B; // { x: number } AND { y: string }Type Narrowing
TypeScript narrows types through control flow:
function process(value: string | number) {
if (typeof value === "string") {
// TypeScript knows value is string here
return value.toUpperCase();
}
// TypeScript knows value is number here
return value.toFixed(2);
}The in Operator
type Fish = { swim: () => void };
type Bird = { fly: () => void };
function move(animal: Fish | Bird) {
if ("swim" in animal) {
animal.swim(); // TypeScript knows it's Fish
} else {
animal.fly(); // TypeScript knows it's Bird
}
}Type Guards
Custom functions that narrow types:
function isString(value: unknown): value is string {
return typeof value === "string";
}
function process(value: string | number) {
if (isString(value)) {
console.log(value.toUpperCase()); // safe
}
}Tips
- Discriminated unions are the #1 pattern in TypeScript — learn them well.
- Use
typeofandinfor basic narrowing. - Create custom type guards (
value is Type) for complex checks. - Prefer unions of string literals over enums for simple cases.
🎉 You have completed the TypeScript course! You now know the core features: types, interfaces, functions, classes, generics, modules, and union types. The next step is to build something real — a small project will teach you more than any tutorial.