Skip to main content

What are Discriminated Unions In TypeScript? Real Life Implementation Examples

ยท 4 min read

Type predicates are used to determine if a given value belongs to a certain type or not, providing a hint to the TypeScript compiler about the type information.

What are Type Predicates ๐Ÿค”โ€‹

In TypeScript, discriminated unions (also known as tagged unions or algebraic data types) are a powerful feature that allows you to create and work with complex types in a type-safe and expressive manner. They are particularly useful for modeling state transitions and handling different cases in a structured way.

A discriminated union is a union type (a type that can be one of several types) where a common property, called the discriminant or tag, is used to determine which specific type within the union is being represented. Each type in the union has a unique value for the discriminant property, enabling TypeScript to distinguish between them and provide appropriate type checking and code completion.

Here's an example illustrating the concept of discriminated unions in TypeScript:

// Define a common "kind" property as the discriminant
interface Circle {
kind: "circle";
radius: number;
}

interface Square {
kind: "square";
sideLength: number;
}

interface Rectangle {
kind: "rectangle";
width: number;
height: number;
}

// Create a discriminated union type
type Shape = Circle | Square | Rectangle;

// Function to calculate the area of a given shape
function getArea(shape: Shape): number {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius * shape.radius;
case "square":
return shape.sideLength * shape.sideLength;
case "rectangle":
return shape.width * shape.height;
default:
// Exhaustiveness checking
const _: never = shape;
return _;
}
}

In this example, Shape is a discriminated union type consisting of Circle, Square, and Rectangle. The kind property acts as the discriminant. The getArea function uses a switch statement to determine the correct formula for calculating the area of a given shape, and TypeScript ensures that all cases are properly handled, providing compile-time type checking and error detection.

TypeScript Course Instructor Image
TypeScript Course Instructor Image

Time To Transition From JavaScript To TypeScript

Level Up Your TypeScript And Object Oriented Programming Skills. The only complete TypeScript course on the marketplace you building TypeScript apps like a PRO.

SEE COURSE DETAILS

How is it Helpful ๐Ÿค”โ€‹

Discriminated unions in TypeScript are helpful and different from normal switch case statements or conditional statements in JavaScript in several ways and the most prominent of them all is exhaustiveness checking.

TypeScript can help you ensure that all possible cases of a discriminated union are handled in a switch statement. If a new case is added to the union type, TypeScript will raise a compilation error if the switch statement doesn't handle the new case. This can help you catch potential bugs early on during development.

Example of Exhaustiveness Checking With Discriminated Unions ๐Ÿค”โ€‹

In the example below, I have added a new interface called Triangle and included it in the Shape discriminated union type. The getArea function, however, has not been updated to handle the new Triangle case. As a result, the TypeScript compiler will raise an error due to the exhaustiveness checking provided by the discriminated unions.

// Define a common "kind" property as the discriminant
interface Circle {
kind: "circle";
radius: number;
}

interface Square {
kind: "square";
sideLength: number;
}

interface Rectangle {
kind: "rectangle";
width: number;
height: number;
}

// Adding a new interface called Triangle
interface Triangle {
kind: "triangle";
base: number;
height: number;
}

// Updating the discriminated union type to include the new Triangle interface
type Shape = Circle | Square | Rectangle | Triangle;

// Function to calculate the area of a given shape
function getArea(shape: Shape): number {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius * shape.radius;
case "square":
return shape.sideLength * shape.sideLength;
case "rectangle":
return shape.width * shape.height;
// The new "triangle" case is missing, causing a compilation error
default:
// Exhaustiveness checking
const _: never = shape;
return _;
}
}

In this example, the TypeScript compiler will raise an error due to the missing "triangle" case in the getArea function's switch statement, enforcing exhaustiveness checking. To fix the error, you would need to add a case to handle the Triangle type:

/** ...
case "triangle":
return (shape.base * shape.height) / 2;
**/

What Can You Do Next ๐Ÿ™๐Ÿ˜Šโ€‹

If you liked the article, consider subscribing to Cloudaffle, my YouTube Channel, where I keep posting in-depth tutorials and all edutainment stuff for ssoftware developers.

YouTube @cloudaffle