ยท best practices

Improve Your Type Safety with Branded Types

Branded types in TypeScript can help catch programming errors early by ensuring that values meet certain criteria before they are used. To create a branded type, you add a readonly property to an existing type. Branded types are especially useful when combined with assertion functions, which validate inputs and assert the branded type after successful validation.

Branded types can help catch programming errors early in the development process by preventing values that don't meet certain criteria from being passed into functions or used in certain contexts. They are a form of defensive coding in TypeScript and can ensure type validation.

Contents

Creating a Branded Type

To create a branded type in TypeScript, you start with an existing type and add a readonly property to it. This property is typically named __brand, __kind, or __type. Since this property is readonly, it's not possible to directly annotate a variable with this type.

Example

type PositiveNumber = number & { __brand: 'PositiveNumber' };
 
function divide(a: number, b: PositiveNumber) {
  return a / b;
}
 
const x: PositiveNumber = 10; // Error: 'number' is not assignable to 'PositiveNumber'
divide(100, x);

Using a Branded Type

Branded types are particularly powerful when used with assertion functions, which can validate an input and assert the branded type after successful validation. In the code below, you can only pass the variable x to the divide function after it has passed the assertPositive check.

Example

// PositiveNumber is a regular type connected with a custom "brand"
type PositiveNumber = number & { __brand: 'PositiveNumber' };
 
// The "divide" function requires "b" to be of type "PositiveNumber"
function divide(a: number, b: PositiveNumber) {
  return a / b;
}
 
// The assertion function returns a type predicate (is PositiveNumber)
// It converts any input into the "PositiveNumber" type if it passes the check
function assertPositiveNumber(x: unknown): asserts x is PositiveNumber {
  if (typeof x === 'number' && x < 0) {
    throw new Error('Number is not greater zero');
  }
}
 
const x = 10;
// After passing this check, "x" becomes "PositiveNumber"
assertPositiveNumber(x);
// A variable of type "PositiveNumber" can now be used with "divide"
divide(100, x); // OK!

This way you can rest assured that your variables and arguments will always be validated before being passed around.

Back to Blog