TypeScript

Lesson 05

Exhaustive checks with never

Make every union case handled so new states fail loudly during development.

Good Code

review-state.ts
type ReviewState =
  | { status: "draft" }
  | { status: "open"; reviewerCount: number }
  | { status: "merged"; mergedBy: string };

function renderReviewState(state: ReviewState) {
  // Assigning to never makes missing union cases a compile error.
  switch (state.status) {
    case "draft":
      return "Draft";
    case "open":
      return "Open for " + state.reviewerCount + " reviewers";
    case "merged":
      return "Merged by " + state.mergedBy;
    default: {
      const neverState: never = state;
      return neverState;
    }
  }
}

Bad Code

review-state.ts
type ReviewState =
  | { status: "draft" }
  | { status: "open"; reviewerCount: number }
  | { status: "merged"; mergedBy: string };

function renderReviewState(state: ReviewState) {
  // Generic fallback hides states the code forgot to handle.
  if (state.status === "draft") {
    return "Draft";
  }

  if (state.status === "open") {
    return "Open for " + state.reviewerCount + " reviewers";
  }

  return "Unknown state";
}

Review Notes

What to review

Good Code

The good version makes the final branch impossible when all union cases are handled, so adding a new state creates a compile-time reminder.

Bad Code

The bad version hides missing cases behind a generic fallback, which can make new product states render as unknown instead of asking the developer to handle them.

Takeaways

  • Use a `never` assignment in the final branch to make missing union cases a type error.