Discriminated unions help you work with different types of data in one place safely. They let you tell which type you have by checking a special property.
Discriminated unions in Typescript
type Shape =
| { kind: "circle"; radius: number }
| { kind: "square"; size: number };
function area(shape: Shape) {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
case "square":
return shape.size ** 2;
}
}The kind property is the discriminant that tells TypeScript which type it is.
Use a switch or if to check the discriminant and handle each case.
type property.type Animal =
| { type: "dog"; barkVolume: number }
| { type: "cat"; livesLeft: number };type to know if the animal is a dog or cat and use the right property.function speak(animal: Animal) { if (animal.type === "dog") { console.log(`Bark volume: ${animal.barkVolume}`); } else { console.log(`Lives left: ${animal.livesLeft}`); } }
status as the discriminant.type Response =
| { status: "success"; data: string }
| { status: "error"; message: string };This program defines a Vehicle type that can be a car or a bike. The describe function uses the kind property to know which type it is and returns a description. We create a car and a bike and print their descriptions.
type Vehicle =
| { kind: "car"; wheels: 4; brand: string }
| { kind: "bike"; wheels: 2; hasBell: boolean };
function describe(vehicle: Vehicle) {
switch (vehicle.kind) {
case "car":
return `Car brand: ${vehicle.brand}, wheels: ${vehicle.wheels}`;
case "bike":
return `Bike with bell: ${vehicle.hasBell}, wheels: ${vehicle.wheels}`;
}
}
const myCar: Vehicle = { kind: "car", wheels: 4, brand: "Toyota" };
const myBike: Vehicle = { kind: "bike", wheels: 2, hasBell: true };
console.log(describe(myCar));
console.log(describe(myBike));Always use a unique string literal property as the discriminant for clear type checking.
Discriminated unions make your code safer by letting TypeScript know exactly which type you are working with.
Discriminated unions use a special property to tell types apart.
They help write clear and safe code when working with multiple types.
Use switch or if to check the discriminant and handle each case.
Practice
What is the main purpose of a discriminated union in TypeScript?
Solution
Step 1: Understand discriminated unions
Discriminated unions use a shared property (called the discriminant) to identify which type an object belongs to.Step 2: Purpose of discriminated unions
This helps TypeScript safely narrow types in code by checking that property.Final Answer:
To distinguish between different object types using a common property -> Option DQuick Check:
Discriminated union = common property to tell types apart [OK]
- Thinking discriminated unions convert types automatically
- Confusing with simple unions of primitives
- Believing they enforce one single type only
Which of the following is the correct syntax to define a discriminated union type in TypeScript?
type Shape = ?Solution
Step 1: Identify discriminant property
type Shape = { kind: 'circle'; radius: number } | { kind: 'square'; side: number } uses a common propertykindwith literal types ('circle' or 'square') to discriminate.Step 2: Check union structure
type Shape = { kind: 'circle'; radius: number } | { kind: 'square'; side: number } correctly uses a union of two object types distinguished bykind.Final Answer:
type Shape = { kind: 'circle'; radius: number } | { kind: 'square'; side: number } -> Option AQuick Check:
Discriminated union = union with common literal property [OK]
- Using intersection (&) instead of union (|)
- Missing literal type for discriminant property
- Using optional properties without discriminant
What will be the output of the following TypeScript code?
type Animal =
| { type: 'dog'; barkVolume: number }
| { type: 'cat'; livesLeft: number };
function describe(animal: Animal) {
switch (animal.type) {
case 'dog':
return `Dog barks at volume ${animal.barkVolume}`;
case 'cat':
return `Cat has ${animal.livesLeft} lives left`;
}
}
console.log(describe({ type: 'cat', livesLeft: 7 }));Solution
Step 1: Analyze the input object
The input is{ type: 'cat', livesLeft: 7 }, so thetypeis 'cat'.Step 2: Follow the switch case
The switch matches 'cat' case and returns`Cat has ${animal.livesLeft} lives left`, which becomes 'Cat has 7 lives left'.Final Answer:
Cat has 7 lives left -> Option AQuick Check:
Switch on discriminant returns correct string [OK]
- Confusing 'dog' and 'cat' cases
- Expecting runtime error due to union
- Ignoring the discriminant property
Identify the error in this TypeScript code using discriminated unions:
type Vehicle =
| { kind: 'car'; wheels: number }
| { kind: 'bike'; pedals: number };
function getWheels(vehicle: Vehicle) {
if (vehicle.kind === 'car') {
return vehicle.wheels;
} else {
return vehicle.wheels;
}
}Solution
Step 1: Check property access in else branch
In the else branch, the code accessesvehicle.wheels, butbiketype has nowheelsproperty.Step 2: Understand TypeScript error
This causes a type error becausewheelsis not defined for{ kind: 'bike'; pedals: number }.Final Answer:
Property 'wheels' does not exist on type 'bike' object -> Option BQuick Check:
Accessing wrong property on union member causes error [OK]
- Assuming all union members share same properties
- Ignoring TypeScript error messages
- Thinking switch is mandatory over if
Given the following discriminated union, how can you safely add a new shape type rectangle with width and height properties and update the area function accordingly?
type Shape =
| { kind: 'circle'; radius: number }
| { kind: 'square'; side: number };
function area(shape: Shape): number {
switch (shape.kind) {
case 'circle':
return Math.PI * shape.radius ** 2;
case 'square':
return shape.side ** 2;
}
}Solution
Step 1: Extend the union type
Add the new shape type with discriminantkind: 'rectangle'and propertieswidthandheightto theShapeunion.Step 2: Update the area function
Add a new case in the switch for 'rectangle' that returnsshape.width * shape.heightto correctly calculate the area.Final Answer:
Add { kind: 'rectangle'; width: number; height: number } to Shape union and add case 'rectangle' in switch returning width * height -> Option CQuick Check:
Extend union and handle new case in switch [OK]
- Forgetting to update the function for new type
- Removing existing cases accidentally
- Not using discriminant property in new type
Ready to prove it?
Pass the test and this pattern moves unknown
