Skip to content
advanced Phase 9 · TypeScript Async & Error Handling

Result & Either Patterns

Model success/failure with discriminated union Result types.

1h
0 problems
Topic Progress 0%

Why a Result Type

Why a Result Type

throw carries no type information — callers cannot see failure without reading the body, and try/catch cannot distinguish error kinds statically:

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

async function parseConfig(raw: string): Promise<Result<Config, ParseIssue>> {
  try {
    return { ok: true, value: JSON.parse(raw) as Config };
  } catch (err) {
    return { ok: false, error: toIssue(err) };
  }
}

const r = await parseConfig(text);
if (r.ok) {
  r.value;    // Config  — narrowed by the literal discriminant
} else {
  r.error;    // ParseIssue
}

The ok: true/ok: false literal types are what make narrowing work; a status: string field would not. Failures become values you must handle, which flows through function signatures and shows up in every call site.

Combinators Without Any

Combinators Without Any

Small generic helpers chain Results fluently:

const map = <T, U, E>(r: Result<T, E>, f: (v: T) => U): Result<U, E> =>
  r.ok ? { ok: true, value: f(r.value) } : r;

const andThen = <T, U, E>(
  r: Result<T, E>,
  f: (v: T) => Result<U, E>,
): Result<U, E> => (r.ok ? f(r.value) : r);

const unwrapOr = <T, E>(r: Result<T, E>, fallback: T): T =>
  r.ok ? r.value : fallback;

const fromThrowable = <T>(f: () => T): Result<T, unknown> => {
  try { return { ok: true, value: f() }; }
  catch (err) { return { ok: false, error: err }; }
};

map transforms success; andThen (flatMap) sequences operations that themselves can fail — the difference mirrors Promise then chains. match collapses to a single output:

const msg = r.ok ? `ok: ${r.value}` : `failed: ${r.error.message}`;

Pitfalls and Interview Angles

Pitfalls and Interview Angles

  • Mixed styles: Result plus still-throwing helpers gives double handling — pick per layer (Result for domain failures, exceptions for programmer errors).
  • E/E unknown: choose E = unknown at untrusted boundaries and narrow; E = Error where you own construction. Never any — it kills the point.
  • Exhaustiveness: a final never check catches missed variants when you add a third state:
function assertNever(x: never): never {
  throw new Error(`unhandled: ${JSON.stringify(x)}`);
}
switch (r.kind) { case "ok": ...; case "err": ...; default: return assertNever(r); }
  • Interop: libraries expect throws — unwrap at the outermost boundary with a policy mapping E to status codes.

Interview angle: refactor getUser(id: string): User (throws NotFoundError, NetworkError) into a Result, then show narrowing and the HTTP mapping.

Quiz

1. Why must a Result discriminant be a literal type like ok: true?

Question 1 options

2. What distinguishes andThen from map?

Question 2 options

3. When are exceptions still appropriate alongside Result types?

Question 3 options

Flashcards

Question

Result<T, E>

Answer

{ok:true;value:T} | {ok:false;error:E} — failure as a typed value instead of a throw.

Question

map vs andThen

Answer

map applies T→U to success; andThen applies T→Result<U,E> to sequence fallible steps.

Question

Discriminated narrowing

Answer

if (r.ok) narrows because ok carries the literal true/false, not string.

Question

assertNever

Answer

Takes never; called in the default branch to prove every union member was handled.

Revision Notes

Key Takeaways

  • 1. Result makes expected failures visible in signatures and forces handling at call sites
  • 2. map/andThen/unwrapOr compose without nesting or any-casts
  • 3. Keep one style per layer and unwrap to throws only at framework boundaries

Interview Tips

  • • Convert a throwing function live: define the union, show narrowing, then map errors to HTTP codes
  • • Contrast Result for domain errors with exceptions for programmer errors — interviewers probe the boundary

Cheat Sheet

Result = {ok:true,value}|{ok:false,error}; narrow via literal ok; map T→U, andThen T→Result; assertNever on default; unwrap at framework edge.

Continue Learning