๐ŸŽ“ A1Learner
๐Ÿ”
0 of 42 lessons completed

TypeScript Results, Not Exceptions

A Result is a discriminated union with a success shape and a failure shape, told apart by the ok tag. A function returns a Result instead of throwing, so the caller must handle both shapes.

Returning a Result instead of throwing

divide returns a failure shape for a zero divisor and a success shape otherwise. The check on ok reads the value safely.

type Result<T, E> = { ok: true; value: T } | { ok: false; error: E };

function divide(a: number, b: number): Result<number, string> {
  if (b === 0) {
    return { ok: false, error: 'Division by zero' };
  }
  return { ok: true, value: a / b };
}

const outcome = divide(10, 2);
if (outcome.ok) {
  console.log(outcome.value);
} else {
  console.log(outcome.error);
}

Handling both branches

A switch on the ok tag handles the success input and the failure input. Each branch reads only the field its shape holds.

type Result<T, E> = { ok: true; value: T } | { ok: false; error: E };

function parseWhole(text: string): Result<number, string> {
  const value = Number(text);
  if (Number.isNaN(value)) {
    return { ok: false, error: 'Not a number: ' + text };
  }
  return { ok: true, value };
}

function showParsed(text: string): void {
  const outcome = parseWhole(text);
  switch (outcome.ok) {
    case true:
      console.log('Value: ' + outcome.value);
      break;
    case false:
      console.log('Failed: ' + outcome.error);
      break;
  }
}

showParsed('42');
showParsed('abc');

Example

type Result<T, E> = { ok: true; value: T } | { ok: false; error: E };

function halfIfPositive(value: number): Result<number, string> {
  if (value < 0) {
    return { ok: false, error: 'Negative value' };
  }
  return { ok: true, value: value / 2 };
}

function showHalf(value: number): void {
  const outcome = halfIfPositive(value);
  if (outcome.ok) {
    console.log('Half: ' + outcome.value);
  } else {
    console.log('Failed: ' + outcome.error);
  }
}

showHalf(10);
showHalf(-4);

Key points

  • The success shape is { ok: true, value: T } and the failure shape is { ok: false, error: E }.
  • Checking the ok tag narrows the union, so value exists only in the success branch and error only in the failure branch.
  • A Result makes failure part of the return type, visible at every call site.
  • Handling both branches covers the failure without a try block or a catch variable.

In this example, one function returns a Result for a good input and a bad one, and the caller prints a line for each branch of the union.

Note

Throwing still suits failures the caller cannot handle locally, while a Result suits failures the caller expects.

Try it Yourself

Edit the code, then press Run.

Running is simulated ยท TypeScript Playground

Exercise

How does a caller read the value of a Result safely?