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")); // 8080Callers 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
- The Problem with Throwing Errors
- Modeling Result Types
- Option and Maybe Types
- Railway-Oriented Programming