TypeScript Result Types - and Why You Should Use Them

Date: 2025-07-04 | build | create | result | tech | typescript |

DISCLOSURE: If you buy through affiliate links, I may earn a small commission. (disclosures)

Previously we discussed why errors as values are often preferable to exceptions:

Results are a common first step towards using errors as values so in this post we'll walk through a simple Result type in TypeScript.

What is a Result type?

Result types are an ergonomic, universal pattern for representing a type that can be either a Success or Failure.

Result<Success, Failure> says that on success you'll get a Success type and on failure you'll get a Failure type. This allows you to ergonomically define both the success and failure cases in the same codepath and lets you leverage the type system for type refinement - to determine the type you must check if it succeeded or not. Because results are just values, they don't break the type system or control flow like exceptions.

Result types provide an alternative to throwing exceptions that the type system understands, follows the same control flows as normal code, and is relatively simple to use.

A simple TypeScript Result type

Here's what a simple Result type can look like in TypeScript:

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

function success<T>(data: T): Result<T, never> {
    return { success: true, data }
}

function failure<E>(error: E): Result<never, E> {
    return { success: false, error }
}

It has two cases, discriminated by the value of success:

  • Success - When success is true, we have a success case that carries data of type T
  • Failure - When success is false, we have a failure case that carries error of type E

To create these types you can use the helper functions success and failure

  • Success - success(data)
  • Failure - failure(error)

Result usage examples

Now some examples for how you might use a Result type.

Here we have a function divide that will divide two numbers. If it sees that we'll be dividing by zero, it returns a failure. Otherwise it does the division and returns a success. We define this on the function with Result<number, string>.

Code:

function divide(a: number, b: number): Result<number, string> {
    if (b === 0) {
        return failure("Division by zero")
    }
    return success(a / b)
}

const resultToString = (result: Result<any, any>): string => {
    if(result.success) {
        return `Success: ${result.data}`
    } else {
        return `Failure: ${result.error}`
    }
}

const result_10over2 = divide(10, 2);
console.log("10 / 2: ", resultToString(result_10over2))

const result_10over0 = divide(10, 0) 
console.log("10 / 0: ", resultToString(result_10over0))

Next

Results are one of my favorite programming patterns as I believe it makes most code more robust, performant, and composable which are key attributes of quality systems (here's an example in Python).

If you're curious to dive deeper, there are full libraries that are geared towards errors as values and provide many more functions and constructs to help you out - True Myth and neverthrow being two popular ones.

If you liked this post you might also like:

Want more like this?

The best way to support my work is to like / comment / share for the algorithm and subscribe for future updates.