Skip to content
beginner Phase 1 · TypeScript Fundamentals

Unions & Enums

Use union types, literal types, and enumerations for flexible type design.

1h
0 problems
Topic Progress 0%

Union Types

Union Types

Union types allow a variable to hold one of several types.

Basic Unions

// A variable can be string OR number
let id: string | number;
id = 'abc-123'; // OK
id = 42;        // OK
// id = true;   // Error

// Function with union parameter
function formatId(id: string | number): string {
  if (typeof id === 'string') {
    return id.toUpperCase();
  }
  return id.toString().padStart(5, '0');
}

formatId('abc');  // 'ABC'
formatId(42);     // '00042'

Union of Object Types

interface Circle {
  kind: 'circle';
  radius: number;
}

interface Square {
  kind: 'square';
  side: number;
}

type Shape = Circle | Square;

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

Nullable Types

// With strictNullChecks
let name: string | null = 'Alice';
name = null; // OK
// name = undefined; // Error

function greet(name: string | null): string {
  return name ? `Hello, ${name}` : 'Hello, stranger';
}

Enums

Enums

Enums define a set of named constants.

Numeric Enums

enum Direction {
  Up,      // 0
  Down,    // 1
  Left,    // 2
  Right    // 3
}

const move: Direction = Direction.Up;
console.log(move); // 0

// Explicit values
enum HttpStatus {
  OK = 200,
  NotFound = 404,
  ServerError = 500
}

function getStatus(code: HttpStatus): string {
  switch (code) {
    case HttpStatus.OK: return 'Success';
    case HttpStatus.NotFound: return 'Not Found';
    case HttpStatus.ServerError: return 'Server Error';
  }
}

String Enums

enum Color {
  Red = 'RED',
  Green = 'GREEN',
  Blue = 'BLUE'
}

const c: Color = Color.Red;
console.log(c); // 'RED'

// String enums are better for debugging and serialization

Heterogeneous Enums

enum BooleanLike {
  No = 0,
  Yes = 'YES'
}

Enums in Objects

enum Status {
  Active = 'active',
  Inactive = 'inactive',
  Pending = 'pending'
}

// Use enums as object keys
const translations: Record<Status, string> = {
  [Status.Active]: 'Activo',
  [Status.Inactive]: 'Inactivo',
  [Status.Pending]: 'Pendiente'
};

Discriminated Unions

Discriminated Unions

Use a common literal property (discriminant) to distinguish between union members.

Pattern with kind Discriminant

interface TextMessage {
  kind: 'text';
  content: string;
}

interface ImageMessage {
  kind: 'image';
  url: string;
  width: number;
  height: number;
}

interface VideoMessage {
  kind: 'video';
  url: string;
  duration: number;
}

type Message = TextMessage | ImageMessage | VideoMessage;

function processMessage(msg: Message): string {
  switch (msg.kind) {
    case 'text':
      return `Text: ${msg.content}`;
    case 'image':
      return `Image: ${msg.width}x${msg.height}`;
    case 'video':
      return `Video: ${msg.duration}s`;
  }
}

Exhaustive Checking

function assertNever(x: never): never {
  throw new Error(`Unexpected value: ${x}`);
}

function processMessage(msg: Message) {
  switch (msg.kind) {
    case 'text': return msg.content;
    case 'image': return msg.url;
    case 'video': return msg.url;
    default:
      return assertNever(msg); // compile error if case missing
  }
}

Real-World Example: API Response

interface SuccessResponse {
  status: 'success';
  data: unknown;
}

interface ErrorResponse {
  status: 'error';
  message: string;
  code: number;
}

interface LoadingResponse {
  status: 'loading';
}

type ApiResponse = SuccessResponse | ErrorResponse | LoadingResponse;

function handleResponse(res: ApiResponse) {
  switch (res.status) {
    case 'success': return res.data;
    case 'error': throw new Error(res.message);
    case 'loading': return null;
  }
}

Const Enums and Best Practices

Const Enums and Best Practices

Const Enums

// Const enums are inlined at compile time - no runtime object created
const enum Direction {
  Up = 'UP',
  Down = 'DOWN',
  Left = 'LEFT',
  Right = 'RIGHT'
}

const move = Direction.Up;
// Compiles to: const move = 'UP';

When to Use What

Use Case Recommendation
Named constants Enums or union of literals
Finite states Discriminated unions
Configuration Const object with as const
External data Union types

Union vs Enum

// Union of literals (preferred for most cases)
type Status = 'active' | 'inactive' | 'pending';

// Enum (better when you need runtime values or iteration)
enum StatusEnum {
  Active = 'active',
  Inactive = 'inactive',
  Pending = 'pending'
}

// Const object (good middle ground)
const Status = {
  Active: 'active',
  Inactive: 'inactive',
  Pending: 'pending'
} as const;
type Status2 = typeof Status[keyof typeof Status];

Common Pitfalls

// Numeric enums have reverse mapping
color enum Color {
  Red,
  Green,
  Blue
}
Color[0]; // 'Red' - reverse mapping exists

// This can cause unexpected behavior
if (Color.Red === 0) {
  console.log('This is true');
}

// Avoid numeric enums for this reason - use string enums instead
  • Prefer string enums over numeric enums for clarity
  • Use discriminated unions instead of type guards
  • Use const enum when you need compile-time inlining