Mastering TypeScript's Conditional Types for Advanced Error Handling Patterns

Learn how to use TypeScript's conditional types to create advanced and type-safe error handling patterns. This beginner-friendly guide explains concepts with practical examples.

Error handling is a crucial part of writing reliable applications. In TypeScript, conditional types allow you to create flexible, type-safe error handling patterns that adapt based on the error context. This guide will walk you through the basics of conditional types and show you how to apply them for advanced error management.

Conditional types in TypeScript work like `if-else` statements for types. They check a condition and return one type if true and another if false. This helps us create more precise error types depending on specific conditions.

typescript
type IsString<T> = T extends string ? "Yes" : "No";

// Usage
type Test1 = IsString<string>; // "Yes"
type Test2 = IsString<number>; // "No"

Now, let's see how to use conditional types for error handling. Imagine you have a function that fetches data and returns either a success type or an error type. You want to define a generic error type that changes based on whether the error is a network error or a validation error.

typescript
interface NetworkError {
  type: "network";
  message: string;
  statusCode: number;
}

interface ValidationError {
  type: "validation";
  message: string;
  field: string;
}

// Conditional type to pick the shape based on error type

type AppError<T extends string> =
  T extends "network" ? NetworkError :
  T extends "validation" ? ValidationError :
  { type: "unknown"; message: string };

// Usage examples

const error1: AppError<"network"> = {
  type: "network",
  message: "Failed to connect",
  statusCode: 503,
};

const error2: AppError<"validation"> = {
  type: "validation",
  message: "Invalid email",
  field: "email",
};

This conditional typing helps you create a strict, discriminated union of errors without writing repetitive code. The `AppError` type adapts its shape depending on the error category you provide.

For a more dynamic and reusable pattern, you can combine conditional types with utility types and generics to extract error information from functions to get strongly typed error handling.

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

function fetchData(): Result<string, AppError<"network" | "validation">> {
  // Simulated error result
  return {
    success: false,
    error: {
      type: "network",
      message: "Timeout",
      statusCode: 504,
    },
  };
}

const result = fetchData();

if (!result.success) {
  // TypeScript will narrow the error type as one of NetworkError or ValidationError
  if (result.error.type === "network") {
    console.log("Network issue:", result.error.statusCode);
  } else if (result.error.type === "validation") {
    console.log("Validation error on field:", result.error.field);
  }
}

By mastering conditional types, you can build flexible error types that improve code readability and help avoid bugs. TypeScript’s powerful type system gives you the ability to model error handling patterns specific to your application needs and catch mistakes during development rather than at runtime.

Start experimenting with conditional types today to enhance your error handling strategies and write safer, more maintainable TypeScript code.