TypeScript Advanced Types: Union, Intersection, Literal, Guards & Discriminated Unions
1. What are union and intersection types in TypeScript?
Union Types: Allow a value to be one of several types, denoted by |.
- Syntax:
type UnionType = Type1 | Type2. - Use Case: Variables that can hold multiple possible types (e.g.,
string | number).
Intersection Types: Combine multiple types into one, requiring all properties, denoted by &.
- Syntax:
type IntersectionType = Type1 & Type2. - Use Case: Combining interfaces for objects with properties from multiple types.
Can you give an example of union and intersection types?
// Union and Intersection Types
type ID = string | number;
interface Person {
name: string;
}
interface Employee {
employeeId: number;
}
// Intersection type
type Staff = Person & Employee;
// Union type usage
function displayId(id: ID) {
console.log(`ID: ${id}`);
}
// Intersection type usage
const staff: Staff = {
name: "kristal",
employeeId: 101
};
console.log(`Staff: ${staff.name}, ID: ${staff.employeeId}`);
// Examples
displayId("A123"); // ID: A123
displayId(456); // ID: 456
2. What are literal types and type aliases in TypeScript?
Literal Types: Restrict a value to a specific literal (e.g., "success", 42).
- Often used with unions (e.g.,
"success" | "error"). - Use Case: Enforcing specific values for variables or parameters.
Type Aliases: Create reusable type definitions with the type keyword.
- Syntax:
type AliasName = TypeDefinition. - Use Case: Simplifying complex types or reusing types across the codebase.
Can you give an example of literal types and type aliases?
// Literal Types and Type Aliases
type Status = "success" | "error" | "pending";
type User = {
id: number;
status: Status;
};
function setStatus(status: Status): void {
console.log(`Status set to: ${status}`);
}
const user: User = {
id: 1,
status: "success"
};
console.log(`User: ID=${user.id}, Status=${user.status}`);
// Examples
setStatus("pending"); // Status set to: pending
// setStatus("invalid"); // Error: Argument not assignable to Status
3. What are type guards and type narrowing in TypeScript?
Type Guards: Mechanisms to check an object's type at runtime, narrowing its type within a block.
- Common Guards:
typeof,instanceof,in, or custom user-defined guards.
Type Narrowing: The process of refining a broader type (e.g., union type) to a more specific type based on checks.
- Use Case: Safely accessing properties or methods specific to a type.
Can you give an example of type guards and type narrowing?
// Type Guards and Type Narrowing
type ID = string | number;
function processId(id: ID) {
// Type guard with typeof
if (typeof id === "string") {
console.log(`String ID: ${id.toUpperCase()}`);
} else {
console.log(`Number ID: ${id.toFixed(2)}`);
}
}
interface User {
name: string;
}
interface Admin {
name: string;
role: string;
}
// User-defined type guard
function isAdmin(user: User | Admin): user is Admin {
return "role" in user;
}
function displayUser(user: User | Admin) {
if (isAdmin(user)) {
console.log(`Admin: ${user.name}, Role: ${user.role}`);
} else {
console.log(`User: ${user.name}`);
}
}
// Examples
processId("A123"); // String ID: A123
processId(456); // Number ID: 456.00
displayUser({ name: "kristal" }); // User: kristal
displayUser({ name: "Sashi", role: "manager" }); // Admin: Sashi, Role: manager
4. What are discriminated unions in TypeScript?
Discriminated Unions: A pattern where a union type has a common property (discriminator) to distinguish between types.
- Structure: Each type in the union has a literal property (e.g.,
kind) with a unique value. - Syntax: Combine interfaces with a shared discriminator and use type guards.
- Use Case: Handling different shapes of data in a type-safe way (e.g., API responses, event types).
Can you give an example of discriminated unions?
// Discriminated Unions
interface SuccessResponse {
kind: "success";
data: string;
}
interface ErrorResponse {
kind: "error";
message: string;
}
type ApiResponse = SuccessResponse | ErrorResponse;
function handleResponse(response: ApiResponse) {
switch (response.kind) {
case "success":
console.log(`Success: ${response.data}`);
break;
case "error":
console.log(`Error: ${response.message}`);
break;
}
}
// Examples
const success: ApiResponse = {
kind: "success",
data: "Operation completed"
};
const error: ApiResponse = {
kind: "error",
message: "Operation failed"
};
handleResponse(success); // Success: Operation completed
handleResponse(error); // Error: Operation failed
5. Can you provide a comprehensive example of advanced TypeScript types?
Project Structure:
ts-advanced-types/
├── src/
│ └── main.ts
├── tsconfig.json
└── package.json
main.ts:
// Comprehensive TypeScript Types Example
// Union and Literal Types
type Status = "active" | "inactive";
type ID = string | number;
// Type Alias
type User = {
id: ID;
status: Status;
name: string;
};
// Intersection Type
interface Employee {
employeeId: number;
}
type Staff = User & Employee;
// Discriminated Union
interface ActiveUser {
kind: "active";
lastLogin: Date;
}
interface InactiveUser {
kind: "inactive";
deactivationReason: string;
}
type UserStatus = ActiveUser | InactiveUser;
// Type Guard
function isActiveUser(user: UserStatus): user is ActiveUser {
return user.kind === "active";
}
// Processing Function
function processUser(user: Staff | UserStatus) {
// Type narrowing with discriminated union
if ("kind" in user) {
if (isActiveUser(user)) {
console.log(`Active User: Last login ${user.lastLogin.toISOString()}`);
} else {
console.log(`Inactive User: Reason ${user.deactivationReason}`);
}
} else {
// Type narrowing with union type
const idDisplay = typeof user.id === "string" ? user.id.toUpperCase() : user.id.toFixed(2);
console.log(`Staff: ${user.name}, ID: ${idDisplay}, Status: ${user.status}, Employee ID: ${user.employeeId}`);
}
}
// Examples
const staff: Staff = {
id: "A123",
status: "active",
name: "kristal",
employeeId: 101
};
const active: UserStatus = {
kind: "active",
lastLogin: new Date("2025-09-15T11:06:00.000Z")
};
const inactive: UserStatus = {
kind: "inactive",
deactivationReason: "Non-compliance"
};
processUser(staff);
processUser(active);
processUser(inactive);
6. What are common mistakes in advanced TypeScript types?
- Union Types: Performing operations not safe for all union types (e.g., calling
toUpperCaseonstring | number). - Intersection Types: Combining incompatible types, causing
neveror errors. - Literal Types/Type Aliases: Using literals without unions, limiting flexibility.
- Type Guards/Narrowing: Incomplete guards, leading to runtime errors despite type safety.
- Discriminated Unions: Forgetting the discriminator property, breaking type narrowing.
- General: Overusing advanced types, reducing code readability.
7. What are best practices for advanced TypeScript types?
- Union Types: Use type guards to safely access union type properties. Keep union types minimal to reduce complexity.
- Intersection Types: Use for combining interfaces with clear, compatible structures. Avoid intersections with conflicting properties.
- Literal Types/Type Aliases: Use literal types with unions for finite value sets (e.g., statuses). Prefer type aliases for simple types; interfaces for extensible objects.
- Type Guards/Narrowing: Use
typeof,instanceof,in, or custom guards appropriately. Ensure guards cover all possible types in a union. - Discriminated Unions: Always include a unique discriminator (e.g.,
kind). Useswitchoriffor clear type narrowing. - General: Follow TypeScript naming conventions (e.g., CamelCase for interfaces/types). Use
tscwithstrictmode (strict: trueintsconfig.json). Document types with JSDoc or comments for clarity. Test with edge cases (e.g., invalid types, missing properties). Use linters (e.g.,eslint) with TypeScript plugins for consistency.