Search⌘ K

Discriminated Unions and Exhaustive Checks

Explore discriminated unions and exhaustive checks in TypeScript to model structured variants with tagged unions. Learn how to ensure every case is handled using the never type for safer, scalable type composition.

We’ve narrowed union types using built-in checks like typeof, and even written our own custom type guards to distinguish structured variants at runtime. That gave us fine-grained control. But sometimes, the shape of the data itself tells us everything we need to know without writing a single guard.

Think server responses. Think user actions. Think result states. These aren’t just values floating around—they’re structured variants. Each one carries a tag that tells us exactly what it is. And once we know that, the rest of its shape falls into place.

If we want to model those cleanly and make sure we never miss a case—we need discriminated unions.

Structuring union types with tags

discriminated union is a union of object types that all share one literal field, often called typekind, or status. This field is the discriminant: a tag that tells TypeScript exactly which shape we’re working with.

To see this in action, imagine a form submission. The result could be a success or an error:

TypeScript 5.8.3
type Success = {
type: "success";
data: string;
};
type FormError = {
type: "error";
error: string;
};
type FormResult = Success | FormError;
function handleResult(result: FormResult) {
if (result.type === "success") {
console.log("Submitted:", result.data);
} else {
console.log("Error:", result.error);
}
}
// Sample calls
handleResult({ type: "success", data: "Form submitted!" });
handleResult({ type: "error", error: "Missing required fields." });

Explanation:

  • Lines 1–9: Define two object ...