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 |.

Intersection Types: Combine multiple types into one, requiring all properties, denoted by &.

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).

Type Aliases: Create reusable type definitions with the type keyword.

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.

Type Narrowing: The process of refining a broader type (e.g., union type) to a more specific type based on checks.

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.

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?

7. What are best practices for advanced TypeScript types?

  1. Union Types: Use type guards to safely access union type properties. Keep union types minimal to reduce complexity.
  2. Intersection Types: Use for combining interfaces with clear, compatible structures. Avoid intersections with conflicting properties.
  3. 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.
  4. Type Guards/Narrowing: Use typeof, instanceof, in, or custom guards appropriately. Ensure guards cover all possible types in a union.
  5. Discriminated Unions: Always include a unique discriminator (e.g., kind). Use switch or if for clear type narrowing.
  6. General: Follow TypeScript naming conventions (e.g., CamelCase for interfaces/types). Use tsc with strict mode (strict: true in tsconfig.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.