Suggest an editImprove this articleRefine the answer for “Discriminant property”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)A **discriminant property** is a **shared property** that is present on every member of a union (`union`) and takes **unique literal values**, letting TypeScript distinguish one type variant from another. **Key point:** thanks to the discriminant property, TypeScript automatically narrows the type in each branch, which makes it possible to build `discriminated unions` and safe switch branching.Shown above the full answer for quick recall.Answer (EN)Image## What is a *discriminant property* A **discriminant property** is a **shared property** that is present on every member of a union (`union`) and takes **unique literal values**, letting TypeScript distinguish one type variant from another. > In simpler terms: a discriminant property is a "tag" that tells the compiler *what it is looking at*. ### Example ```javascript type Circle = { kind: "circle"; radius: number; }; type Square = { kind: "square"; side: number; }; type Shape = Circle | Square; ``` Here the `kind` property is the **discriminant property**. It: - exists **on both types**, - takes **unique literal values** (`"circle"`, `"square"`), - and is exactly what TypeScript uses to tell which type is currently in use. ### Usage ```javascript function area(shape: Shape) { if (shape.kind === "circle") { // shape: Circle return Math.PI * shape.radius ** 2; } else { // shape: Square return shape.side ** 2; } } ``` TypeScript sees that `kind` is the discriminant, and it **automatically narrows the type (narrowing)** in each branch. ## How TypeScript "recognizes" the discriminant For a property to count as **discriminant**, three conditions must hold. | Condition | Explanation | |---|---| | 1 | All members of the union (`union`) must be **object types** | | 2 | All of them must share a **key with the same name** (for example, `kind`, `type`, `status`) | | 3 | That key must have a **literal value** (`"circle"`, `"square"`, `"error"`), not just `string` | Example: ```javascript type Loading = { status: "loading" }; type Success = { status: "success"; data: string }; type Error = { status: "error"; message: string }; type Response = Loading | Success | Error; ``` Example (will not be discriminated): ```javascript type Bad = { status: string; data?: string }; type Worse = { status: string; message?: string }; type InvalidUnion = Bad | Worse; // TS cannot narrow by status ``` ## Example with `switch` and an exhaustiveness check ```javascript function handle(response: Response) { switch (response.status) { case "loading": return "Loading..."; case "success": return `Data: ${response.data}`; case "error": return `Error: ${response.message}`; default: const _exhaustive: never = response; return _exhaustive; } } ``` TypeScript knows that `status` is the discriminant, and it will automatically narrow the type in every `case`. If you add a new variant but do not handle it, the compiler will warn you about the error through `never`. ## Common discriminant names You can name the property however you like, but in practice these are the ones used most often. | Name | Example value | |---|---| | `kind` | `"circle"`, `"square"` | | `type` | `"add"`, `"remove"`, `"reset"` | | `status` | `"loading"`, `"success"`, `"error"` | | `variant` | `"light"`, `"dark"` | | `tag` | `"left"`, `"right"` | | `mode` | `"auto"`, `"manual"` | ## Real-world use cases ### 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 []; } } ``` `type` is the discriminant property. ### Loading states ```javascript type State = | { status: "idle" } | { status: "loading" } | { status: "success"; data: string } | { status: "error"; error: string }; function render(state: State) { if (state.status === "loading") return "Loading..."; if (state.status === "error") return `${state.error}`; if (state.status === "success") return `${state.data}`; return "Idle"; } ``` `status` is the discriminant property. ## Why this matters so much Without a discriminant, TypeScript has no way to tell the members of a union apart: ```javascript type A = { name: string }; type B = { age: number }; type AB = A | B; function f(v: AB) { if ("name" in v) { // TS cannot say for certain that v is A console.log(v.name); } } ``` With a discriminant, everything becomes **strictly typed and safe**: ```javascript type A = { type: "A"; name: string }; type B = { type: "B"; age: number }; type AB = A | B; function f(v: AB) { if (v.type === "A") { console.log(v.name); // TS knows that v is A } } ``` ## Summary | Concept | Description | |---|---| | **Discriminant property** | A property shared by all members of a union type that has a unique literal value | | **Why it is needed** | So that TypeScript can automatically narrow the type within a union | | **Common names** | `kind`, `type`, `status`, `variant`, `tag` | | **Key role** | Makes it possible to build "discriminated unions" and write safe switch branches | | **Main condition** | The property's values must be literal and unique | --- **A simple comparison:** > A *discriminant property* is like an object's "passport": > TypeScript looks at the `kind` field and immediately knows which "citizenship" (type) it belongs to.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.