Suggest an editImprove this articleRefine the answer for “What are Discriminated Unions?”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)A **Discriminated Union** is a `union` of several object types that share a common tag property (the discriminant), by which TypeScript can unambiguously determine which exact type it is dealing with. **Key point:** thanks to the discriminant, TypeScript automatically narrows the type in a `switch`/`if`, and a `never` check in `default` guarantees that every variant has been handled.Shown above the full answer for quick recall.Answer (EN)Image## What a Discriminated Union is A **Discriminated Union** is a **union (**`union`**) of several object types** that share a **common tag property (a discriminant)**, by which TypeScript can **unambiguously determine exactly which type it is dealing with**. --- ### Example ```javascript type Circle = { kind: "circle"; radius: number; }; type Square = { kind: "square"; side: number; }; type Shape = Circle | Square; ``` Here: - `Shape` is a **union of two types** (`Circle | Square`), - both have a shared property, `kind`, - the value of `kind` is unique to each type. This is the **discriminant field**. --- ## Why a "discriminant" is needed TypeScript can **narrow the type (narrowing)** based on the value of the `kind` property. ```javascript function area(shape: Shape) { if (shape.kind === "circle") { // Here shape: Circle return Math.PI * shape.radius ** 2; } else { // Here shape: Square return shape.side ** 2; } } ``` TypeScript sees `shape.kind === "circle"` and **automatically** determines that `shape` is now `Circle`. --- ## How this works under the hood TypeScript does the following: 1. Finds the property common to all members of the union (`kind`); 2. Checks that it takes **unique literal values** (`"circle"`, `"square"`); 3. When you check `shape.kind === "circle"`, TS **excludes all other variants** (`Square`). This is **discriminated union narrowing** - automatic type narrowing based on the discriminant. --- ## An example with several variants ```javascript type Shape = | { kind: "circle"; radius: number } | { kind: "square"; side: number } | { kind: "triangle"; base: number; height: number }; function getArea(shape: Shape) { switch (shape.kind) { case "circle": return Math.PI * shape.radius ** 2; case "square": return shape.side ** 2; case "triangle": return (shape.base * shape.height) / 2; default: const _exhaustive: never = shape; return _exhaustive; } } ``` In the `switch` block TypeScript itself **narrows** the type of `shape` at each step. And `never` in `default` guarantees that **all cases are handled**. --- ## Why this is convenient Discriminated unions let you: - build **safe enum-like constructs**; - type **application states**, **API results**, **Redux actions**; - avoid unnecessary `as` casts and manual checks; - guarantee **exhaustive handling of all cases**. --- ## Real-world examples ### 1. Loading states (state machine pattern) ```javascript type LoadingState = { status: "loading" }; type SuccessState = { status: "success"; data: string }; type ErrorState = { status: "error"; message: string }; type RequestState = LoadingState | SuccessState | ErrorState; function render(state: RequestState) { switch (state.status) { case "loading": return "Loading..."; case "success": return `Data: ${state.data}`; case "error": return `Error: ${state.message}`; } } ``` --- ### 2. Redux-like actions ```javascript type Action = | { type: "add"; payload: number } | { type: "remove"; id: string } | { type: "reset" }; function reducer(state: number[], action: Action) { switch (action.type) { case "add": return [...state, action.payload]; case "remove": return state.filter((_, i) => i.toString() !== action.id); case "reset": return []; } } ``` --- ### 3. API responses ```javascript type ApiResponse<T> = | { kind: "ok"; data: T } | { kind: "error"; message: string }; function handleResponse(res: ApiResponse<string>) { if (res.kind === "ok") { console.log("OK:", res.data); } else { console.error("Error:", res.message); } } ``` --- ## An exhaustiveness check (`never` exhaustiveness check) A useful technique: add a check that all variants are handled. ```javascript function checkExhaustive(value: never): never { throw new Error("Unhandled case: " + value); } function draw(shape: Shape) { switch (shape.kind) { case "circle": return "Drawing circle"; case "square": return "Drawing square"; default: return checkExhaustive(shape); // if a new variant appears, TS will produce an error } } ``` --- ## Discriminated unions vs. regular unions | Regular union | Discriminated union | |---|---| | `type A = {a: number} | {b: string}` | | No common marker | There is a common tag field (`kind`) | | TS does not know which variant is active | TS can narrow the type automatically | | Requires manual checks (`'a' in obj`) | A check on `kind` or `switch` is enough | --- ## Summary | Term | Meaning | |---|---| | **Discriminated union** | A union of types with a common discriminant | | **Discriminant** | A common property with unique literal values | | **Why it's needed** | Lets TypeScript automatically narrow the type and check all cases | | **Typical use** | State machines, Redux actions, API responses, error handling | | **Main advantage** | Safe branching without `as`, with full typing and hints | --- **Just remember:** > A Discriminated Union is when different shapes of data have a "passport" (a discriminant), > and TypeScript uses that passport to know exactly what it is looking at.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.