← Back to TypeScript Course | Chapter 18: Error Handling | Lesson 3 of 5

Error Boundaries concept

An error boundary is a design pattern that contains failures at a defined application boundary instead of letting them crash unrelated parts of the program. TypeScript does not provide a universal error-boundary keyword, so boundaries are implemented with mechanisms such as try/catch, middleware, or framework-specific features.

What Is an Error Boundary?

An error boundary is a defined point in an application where a failure gets caught, logged, and converted into a controlled result instead of crashing or propagating unpredictably further up the call stack.

Example: What Is an Error Boundary?

typescript
function boundary<T>(fn: () => T, fallback: T): T {
  try {
    return fn();
  } catch {
    return fallback;
  }
}
console.log(boundary(() => { throw new Error("fail"); }, "default value"));

Boundaries Around Services

A boundary placed around a service call can prevent low-level failures — a database timeout, a malformed API response — from leaking raw implementation details into higher-level application code that shouldn't need to know about them.

Example: Boundaries Around Services

typescript
function callService(): string {
  throw new Error("Database timeout");
}
function boundary<T>(fn: () => T, fallback: T): T {
  try {
    return fn();
  } catch {
    return fallback;
  }
}
console.log(boundary(callService, "service unavailable"));

Boundaries in HTTP Applications

Web applications commonly place an error boundary around request handling so any failure during a request becomes a controlled HTTP response (like a 500 with a safe message) instead of an unhandled crash.

Example: Boundaries in HTTP Applications

typescript
function handleRequest(): { status: number; body: string } {
  try {
    throw new Error("unexpected failure");
  } catch {
    return { status: 500, body: "Internal Server Error" };
  }
}
console.log(handleRequest());

Boundaries and Logging

A good error boundary should record enough detail internally for debugging — stack trace, request context — while returning a much safer, less detailed message to the end user or external caller.

Example: Boundaries and Logging

typescript
function handleRequest() {
  try {
    throw new Error("db connection lost");
  } catch (err) {
    console.log("[internal log]", err); // detailed, for developers
    return { status: 500, body: "Something went wrong" }; // safe, for users
  }
}
console.log(handleRequest());

Designing Useful Boundaries

Place boundaries at a meaningful architectural level, avoid swallowing errors so silently that real bugs go unnoticed, and aim for a boundary that returns a predictable, well-typed result either way.

Example: Designing Useful Boundaries

typescript
function boundary<T>(fn: () => T, fallback: T): T {
  try {
    return fn();
  } catch (err) {
    console.log("Caught at boundary:", err);
    return fallback;
  }
}
console.log(boundary(() => JSON.parse("not json"), null));
🔒

Chapter Quiz — Complete all 5 topics to unlock

0/5 topics done

Complete these topics first:

Login to run this code

C/C++/Java/PHP execution requires a free account. Your code is saved — you'll land right back in the editor after logging in.