SyntaxStudy
Sign Up
TypeScript Exhaustiveness Checking with Enums
TypeScript Beginner 1 min read

Exhaustiveness Checking with Enums

One of the most valuable patterns with enums is exhaustiveness checking — ensuring that a switch statement or conditional chain handles every possible enum value. TypeScript's never type makes this automatic: if a value reaches a branch where it should be never but it is not, the compiler reports an error. This catches bugs when new enum values are added but the handling code is not updated. The assertNever pattern involves a default case in a switch that calls a function expecting a never argument. If the switch is exhaustive, the default case is unreachable and the argument is correctly typed as never. If you add a new enum member without updating the switch, the compiler will error because the unhandled value is not never. This turns what would be a silent runtime bug into a compile-time error. Exhaustiveness checking is not limited to enums — it works with any discriminated union. The principle is the same: TypeScript narrows the type in each branch until, after all cases are handled, what remains must be never. This pattern is a cornerstone of robust TypeScript codebases and is especially important in code that handles user input, API responses, and application state.
Example
// Exhaustiveness helper
function assertNever(value: never, message = "Unhandled case"): never {
    throw new Error(`${message}: ${JSON.stringify(value)}`);
}

enum TrafficLight { Red = "RED", Yellow = "YELLOW", Green = "GREEN" }

function getAction(light: TrafficLight): string {
    switch (light) {
        case TrafficLight.Red:    return "Stop";
        case TrafficLight.Yellow: return "Caution";
        case TrafficLight.Green:  return "Go";
        default:
            // If a new member is added without updating this switch,
            // TypeScript will error here because light is no longer never
            return assertNever(light);
    }
}

// Discriminated union exhaustiveness
type PaymentMethod =
    | { type: "card";   last4: string; brand: string }
    | { type: "paypal"; email: string }
    | { type: "crypto"; address: string; coin: string };

function describePayment(method: PaymentMethod): string {
    switch (method.type) {
        case "card":
            return `${method.brand} card ending in ${method.last4}`;
        case "paypal":
            return `PayPal account ${method.email}`;
        case "crypto":
            return `${method.coin} wallet ${method.address}`;
        default:
            return assertNever(method);
    }
}

console.log(describePayment({ type: "card", last4: "4242", brand: "Visa" }));