Yeda AI Tips · #159

Español

Return a Result Type for Failures You Expect

Stop throwing exceptions for failures you know will happen. When a function parses user input, calls an external API, or reads a file, failure isn't exceptional — it's a normal, expected outcome. Throwing for it hides the error path from the caller: nothing in the function's type says it can fail, so nothing forces anyone to handle it. Return a Result instead — either ok with a value or err with a typed error — and the compiler stops letting people forget.

Why throw hides expected failures

A thrown exception is invisible in the type system. function parse(s: string): User looks total — it always returns a User. But if it throws on bad input, every caller is silently on the hook for a try/catch that TypeScript will never remind them to write. The happy path compiles cleanly and ships; the error path is discovered in production.

Exceptions are for the truly exceptional — the bugs and invariant violations you don't expect and can't sensibly recover from at the call site. Expected failures — validation, network, parsing, missing records — belong in the return type, where they're impossible to ignore.

Return a Result instead

A Result is a union of two shapes: success carrying a value, or failure carrying a typed error.

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

function parseAge(input: string): Result<number, "not_a_number" | "negative"> {
  const n = Number(input);
  if (Number.isNaN(n)) return { ok: false, error: "not_a_number" };
  if (n < 0) return { ok: false, error: "negative" };
  return { ok: true, value: n };
}

const r = parseAge(raw);
if (r.ok) {
  use(r.value);   // r.value is a number here
} else {
  report(r.error); // r.error is the typed error here
}

The ok field is the discriminant. Because it's a literal (true / false), TypeScript narrows the union the moment you check it: inside the if, only value exists; inside the else, only error. You can't read r.value without first proving r.ok — the compiler won't let you skip the failure branch.

When to use which

Failure kindExampleSignal it with
Expected & recoverableBad input, 404, timeout, parse errorReturn a Result
Unexpected / bugNull invariant broken, "impossible" stateThrow (or assert)
Absence, no detail neededLookup miss with one obvious cause`T \undefined`

Power moves

Resources

<div class="cta"> Building an AI feature? Yeda AI designs, audits, and ships production LLM systems. </div>