Error Handling with Types in TypeScript
Error Handling with Types
~16 Min. Lesezeit Zuletzt aktualisiert am August 8, 2026
Our checkOut() method (chapter 14) has so far only returned boolean – success or failure, but NO information about WHY something failed. This chapter shows two better patterns: custom error classes and the "result type" pattern.
A fundamental TypeScript issue: catch(error)
try {
library.checkOut('123');
} catch (error) {
console.log(error.message); // error! error has type 'unknown', NOT 'Error'
}In modern TypeScript (since version 4.4, with strict enabled), error in a catch block has the type unknown by default (chapter 4) – NOT Error, because JavaScript technically allows throwing ANYTHING (throw 'a string', throw 42 are both valid). TypeScript FORCES you to account for that.
try {
library.checkOut('123');
} catch (error) {
if (error instanceof Error) { // type narrowing from chapter 19!
console.log(error.message); // ✓ now allowed - error is recognized as Error
} else {
console.log('Unknown error:', error);
}
}Custom error classes
export class LibraryError extends Error {
constructor(message: string) {
super(message);
this.name = 'LibraryError';
}
}
export class MediumNotFoundError extends LibraryError {
constructor(public readonly isbn: string) {
super(`Medium with ISBN ${isbn} was not found.`);
this.name = 'MediumNotFoundError';
}
}
export class MediumNotAvailableError extends LibraryError {
constructor(public readonly isbn: string, public readonly currentStatus: string) {
super(`Medium ${isbn} is not available (status: ${currentStatus}).`);
this.name = 'MediumNotAvailableError';
}
}EXACTLY inheritance from chapter 13, applied to the built-in Error class – MediumNotFoundError and MediumNotAvailableError carry ADDITIONAL, typed information (isbn, currentStatus) beyond the plain message string.
// In Library.ts:
checkOut(isbn: string): void {
const medium = this.stock.find((m) => m.isbn === isbn);
if (!medium) {
throw new MediumNotFoundError(isbn);
}
if (medium.status !== 'available') {
throw new MediumNotAvailableError(isbn, medium.status);
}
medium.status = 'borrowed';
}try {
library.checkOut('999');
} catch (error) {
if (error instanceof MediumNotFoundError) {
console.log(`Please check the ISBN: ${error.isbn}`); // typed access to isbn!
} else if (error instanceof MediumNotAvailableError) {
console.log(`Status was: ${error.currentStatus}`);
}
}Alternative: the result type pattern
throw/catch has a downside: the function's TYPE itself does NOT show that an error is possible – callers have to read the documentation or "just know". The result type pattern makes errors EXPLICITLY part of the signature:
export type Result<T, E = Error> =
| { success: true; value: T }
| { success: false; error: E };
export function success<T>(value: T): Result<T, never> {
return { success: true, value };
}
export function failure<E>(error: E): Result<never, E> {
return { success: false, error };
}checkOut(isbn: string): Result<void, LibraryError> {
const medium = this.stock.find((m) => m.isbn === isbn);
if (!medium) {
return failure(new MediumNotFoundError(isbn));
}
if (medium.status !== 'available') {
return failure(new MediumNotAvailableError(isbn, medium.status));
}
medium.status = 'borrowed';
return success(undefined);
}
const result = library.checkOut('123');
if (result.success) {
console.log('Checkout successful!'); // TypeScript knows: result.value exists (discriminated union, chapter 9!)
} else {
console.log(`Error: ${result.error.message}`); // TypeScript knows: result.error exists
}Result<T, E> is a discriminated union (chapter 9) COMBINED with generics (chapter 17) – the success boolean is the discriminator. The BIG advantage: the RETURN TYPE Result<void, LibraryError> shows IMMEDIATELY, without reading documentation, that this method CAN fail – a caller reading result.value WITHOUT first checking result.success gets a COMPILE-TIME error, not just a runtime crash.
| Pattern | When it fits |
|---|---|
| throw/catch | Familiar, JavaScript standard, works well for UNEXPECTED, rare errors (programming bugs, corrupted data). |
| Result type | Errors are part of the TYPE signature, handling is ENFORCED by the compiler, ideal for EXPECTED, frequent "failure cases" (validation, business logic rules like "book not available"). |
Tipp: Rule of thumb: throw for GENUINELY EXCEPTIONAL situations (a bug, corrupted data, a network that's completely unreachable), the result type for EXPECTABLE "error paths" that ARE part of the normal program flow ("book already checked out" is not a BUG, but a normal business case). Both patterns can coexist in the same project.