0Pricing
TypeScript Academy · Lesson

The Problem with Throwing Errors

Why exceptions hide failure from the type system.

The Problem with Throwing Errors is a free TypeScript Academy lesson on CoddyKit — lesson 1 of 4. 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, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

Exceptions Are Invisible

When a function throws, its signature does not say so. The type system cannot see which functions may fail, so callers have no compiler nudge to handle errors.

A Throwing Function

This function looks like it always returns a number, but it can throw. The return type number hides the failure case completely.

function parsePort(s: string): number {
  const n = Number(s);
  if (Number.isNaN(n)) throw new Error("bad port");
  return n;
}
console.log(parsePort("8080")); // 8080

Callers Forget to Catch

Nothing forces the caller to wrap the call in try/catch. The mistake compiles cleanly and only shows up as a crash at runtime.

const port = parsePort("oops");
// No compile error; throws at runtime, possibly crashing the app.

try/catch Loses the Type

Even when you do catch, the caught value is typed unknown (or any). The error's shape is not tracked, so handling is guesswork.

try {
  parsePort("x");
} catch (e) {
  // e: unknown -> you must narrow it manually
}

Errors as Values

The alternative is to make failure an ordinary value the function returns, not an exception it throws. Then the return type honestly shows both outcomes.

A Value-Returning Sketch

Instead of throwing, return a tagged object describing success or failure. The caller must inspect it before using the value.

type ParseResult =
  | { ok: true; value: number }
  | { ok: false; error: string };

The Type Now Tells the Truth

A function returning ParseResult advertises that it may fail. The compiler then requires the caller to deal with the failure branch.

function parsePort(s: string): ParseResult {
  const n = Number(s);
  return Number.isNaN(n)
    ? { ok: false, error: "bad port" }
    : { ok: true, value: n };
}

Forced Handling

Because the value could be either branch, you cannot read value without first checking ok. The compiler narrows the type only after the check.

const r = parsePort("oops");
if (r.ok) console.log(r.value);
else console.log("failed:", r.error);

Exceptions Still Have a Place

Truly unexpected, unrecoverable failures (programmer bugs, out-of-memory) can still throw. Errors-as-values shine for expected failures like parsing and validation.

Explicit Over Implicit

Returning errors makes the failure path explicit in the type, visible at the call site, and impossible to ignore by accident, unlike a hidden throw.

Toward Result and Option

This tagged success-or-failure shape generalizes into a reusable Result type, and absence-versus-presence generalizes into Option, both covered next.

Quick Check

Quick check on this lesson.

Recap

Thrown errors are invisible to types: signatures hide failure and callers forget to catch. Returning failure as an explicit value (a tagged success-or-error object) makes the failure path visible in the type and impossible to ignore, leading to Result and Option types.

Frequently asked questions

Is the “The Problem with Throwing Errors” lesson free?

Yes — the full text of “The Problem with Throwing Errors” is free to read here on the web, and the TypeScript Academy course includes 4 lessons in total. 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.

What will I learn in “The Problem with Throwing Errors”?

Why exceptions hide failure from the type system. 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; this is — lesson 1 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “The Problem with Throwing Errors” 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. The Problem with Throwing Errors
  2. Modeling Result Types
  3. Option and Maybe Types
  4. Railway-Oriented Programming
← Back to TypeScript Academy