TypeScript Academy · Lesson

Result/Either style types

Model success/failure explicitly with a Result/Either union, add helpers (map/flatMap/mapError), and keep flows predictable without try/catch everywhere.

Lesson 1 of 28 steps

Result/Either style types is a free TypeScript Academy lesson on CoddyKit. This is lesson 1 of 2. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the TypeScript Academy learning path, and your progress syncs across the web and the CoddyKit app. The TypeScript Academy course includes 2 lessons in total.

Intro

Goal: Make failures visible. You will model results with a discriminated union, compose with helpers, and convert exceptions at the boundary.

  • Result type
  • map/flatMap/mapError
  • HTTP integration

Result core

Create a small Result utility: ok/err constructors plus map, flatMap, mapError for composition.

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

export const ok = <T>(value: T): Ok<T> => ({ ok: true, value })
export const err = <E>(error: E): Err<E> => ({ ok: false, error })

export function map<T, E, U>(r: Result<T, E>, f: (t: T) => U): Result<U, E> {
  return r.ok ? ok(f(r.value)) : r
}
export function flatMap<T, E, U>(r: Result<T, E>, f: (t: T) => Result<U, E>): Result<U, E> {
  return r.ok ? f(r.value) : r
}
export function mapError<T, E, F>(r: Result<T, E>, g: (e: E) => F): Result<T, F> {
  return r.ok ? r : err(g(r.error))
}

Domain errors

Model domain errors as a discriminated union with a tag. Each branch carries precise data for good messages.

export type CreateUserError =
  | { tag: "InvalidEmail"; detail?: string }
  | { tag: "Duplicate"; email: string }

export function validateEmail(email: string): Result<string, CreateUserError> {
  const okEmail = /.+@.+/.test(email)
  return okEmail ? ok(email.toLowerCase()) : err({ tag: "InvalidEmail", detail: "bad format" })
}

const existing = new Set(["ada@example.com"]) // pretend DB
export function saveUser(email: string): Result<{ id: string }, CreateUserError> {
  if (existing.has(email)) return err({ tag: "Duplicate", email })
  return ok({ id: Math.random().toString(36).slice(2) })
}

Composing flows

Compose validations with flatMap. Use a switch with a never check to enforce exhaustiveness at compile time.

export function createUser(flowEmail: string) {
  return flatMap(validateEmail(flowEmail), email => saveUser(email))
}

// Use
const r = createUser("Ada@example.com")
if (r.ok) {
  console.log("id:", r.value.id)
} else {
  switch (r.error.tag) {
    case "InvalidEmail": console.error("invalid email:", r.error.detail); break
    case "Duplicate": console.error("already used:", r.error.email); break
    default: ((x: never) => x)(r.error) // exhaustiveness
  }
}

Exception → Result

At I/O boundaries, wrap exceptions and convert them into Result. Callers keep a single error path to handle.

export async function fromPromise<T>(p: Promise<T>): Promise<Result<T, { tag: "Exception"; message: string }>> {
  try { return ok(await p) }
  catch (e) { return err({ tag: "Exception", message: e instanceof Error ? e.message : String(e) }) }
}

// Example: wrap a fetch
export async function fetchJson<T>(url: string): Promise<Result<T, { tag: "Exception"; message: string }>> {
  return fromPromise(fetch(url).then(r => r.json() as T))
}

HTTP mapping tips

HTTP integration: map domain errors to 400/409; I/O exceptions to 502/503. Keep a tiny mapper function so controllers remain clean and predictable.

Result benefit check

Quick check: Why prefer a discriminated Result union?

Recap

Recap: Define a small Result type, compose with map/flatMap, model domain errors as unions, and convert exceptions at boundaries.

Free to start

Learn TypeScript with an AI tutor — free

Write and run real code in your browser, get instant help from a 24/7 AI tutor, and pick up where you left off on the web or in the app.

Courses
101
Lessons
352

Frequently Asked Questions

Is the “Result/Either style types” lesson free?

Yes — the full text of “Result/Either style types” is free to read here on the web. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the TypeScript Academy course, upgrade to CoddyKit PRO. The TypeScript Academy course includes 2 lessons in total.

What will I learn in “Result/Either style types”?

Model success/failure explicitly with a Result/Either union, add helpers (map/flatMap/mapError), and keep flows predictable without try/catch everywhere. You practise TypeScript Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start TypeScript Academy?

No prior experience is required. TypeScript Academy on CoddyKit is structured for beginners through advanced learners, so you can start here or from the beginning and move at your own pace. This is lesson 1 of 2.

How long does the “Result/Either style types” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this TypeScript Academy lesson?

Yes. Every TypeScript Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. Result/Either style types
  2. Exhaustive error handling with discriminated unions
← Back to TypeScript Academy