5d02e7barticle1 min read
Discriminated unions in practice
Model state as a set of explicit cases so the compiler tells you every place a new case needs handling.
Published
Boolean flags multiply. Two flags give four states, and usually only three of them make sense. A discriminated union names the valid states and makes the rest impossible to represent.
From flags to cases
type Request<T> =
| { status: "idle" }
| { status: "loading" }
| { status: "success"; data: T }
| { status: "error"; error: Error };Each case carries only the data that exists in that state. There is no data to read while loading, so there is no way to read it by mistake.
Exhaustive handling
A switch on the discriminant narrows each branch. Assigning the leftover value to never turns a forgotten case into a compile error.
function label(r: Request<string>) {
switch (r.status) {
case "idle": return "Ready";
case "loading": return "Loading…";
case "success": return r.data;
case "error": return r.error.message;
default: {
const unreachable: never = r;
return unreachable;
}
}
}