Result types

Ok and Err shapes

A result is a union with two honest outcomes: ok with a value, or err with a message. Callers see both cases in the type, so no failure hides behind undefined.

The ok flag drives narrowing. Check it once and the checker knows exactly which fields exist on each branch.

type Result<T> = | { ok: true; value: T } | { ok: false; error: string }; function ok<T>(value: T): Result<T> { return { ok: true, value }; } function err<T>(error: string): Result<T> { return { ok: false, error }; }

Handle both

Every result demands a decision on both branches. An if on the ok flag splits the world cleanly: happy path with the value, sad path with the message.

Handle failures near the screen where a fallback exists. Early returns keep the happy path flat instead of nested three levels deep.

function show(r: Result<string>): string { if (r.ok) { return r.value; } return "Problem: " + r.error; }

Chain results

Chaining runs the next step only when the previous one succeeded. A small helper maps the inner value and passes errors through untouched.

This pattern removes pyramids of nested checks. Each step states its transform while the helper carries the plumbing between steps.

function mapResult<A, B>(r: Result<A>, fn: (v: A) => B): Result<B> { if (!r.ok) { return r; } return ok(fn(r.value)); } const doubled = mapResult(ok(21), (v) => v * 2);

Throw at the edge

Results work best inside logic, while outer layers often expect exceptions. Convert once at the edge: unwrap values on ok and throw a shaped error on err.

One conversion point keeps inner code total and outer code familiar. Debugging stays simple because throws happen in exactly one known place.

function unwrap<T>(r: Result<T>): T { if (!r.ok) { throw new Error(r.error); } return r.value; }

Next: Options objects

Article author: Arthur Isaev