A discriminated (tagged) union is a union of object types that all share a common literal field — the discriminant — which the compiler uses to tell the variants apart and narrow safely.
=
| { : }
| { : ; : }
| { : ; : };
A discriminated (tagged) union is a union of object types that all share a common literal field — the discriminant — which the compiler uses to tell the variants apart and narrow safely.
=
| { : }
| { : ; : }
| { : ; : };
Every member has a status literal. Checking it narrows to exactly one variant, unlocking that variant's fields:
function render(r: Result) {
switch (r.status) {
case "loading": return "...";
case "success": return r.data; // ✅ data exists only here
case "error": return r.message; // ✅ message exists only here
}
}
If you try to access r.data in the loading case, it's a compile error — the type system makes invalid combinations unrepresentable.
function render2(r: Result): string {
switch (r.status) {
case "loading": return "...";
case "success": return r.data;
case "error": return r.message;
default:
const _exhaustive: never = r; // ✅ if you add a variant and forget a case, this errors
return _exhaustive;
}
}
The never assignment forces you to handle every case — add a new status and the compiler points you at every switch that needs updating.
Discriminated unions are the idiomatic way to model state (loading/success/error), events/actions (Redux reducers), and any "one of several shapes" data.
They make illegal states impossible and, with the never trick, give you compile-time exhaustiveness — a huge safety win over loose boolean/optional-field modeling.