TypeScript Never Type: Complete Guide to Exhaustive Type Checking

The TypeScript never type is one of the most powerful yet often misunderstood features in TypeScript’s type system. In this comprehensive guide, we’ll explore how the never type helps create more robust and type-safe applications by ensuring exhaustive type checking.

Whether you’re new to TypeScript or looking to deepen your understanding, this guide will help you master the never type and its practical applications.

Table of Contents

Understanding the Never Type

The never type represents values that never occur. While this might sound abstract, it’s a crucial concept for building type-safe applications. The never type is particularly useful in these scenarios:

  • Functions that never return (throw exceptions or have infinite loops)
  • Exhaustive type checking in switch statements and conditional logic
  • Ensuring unreachable code paths remain unreachable

Functions That Never Return

Let’s look at a basic example of a function that uses the never type:

function throwError(message: string): never {
  throw new Error(message);
}

function infiniteLoop(): never {
  while (true) {
    // Some operation
  }
}
Code language: JavaScript (javascript)

In these examples, the never type accurately represents that these functions never return a value – they either throw an error or continue indefinitely.

Exhaustive Type Checking

One of the most powerful applications of the never type is in exhaustive type checking. Here’s a practical example:

type Shape = Circle | Square | Triangle;

interface Circle {
  kind: 'circle';
  radius: number;
}

interface Square {
  kind: 'square';
  sideLength: number;
}

interface Triangle {
  kind: 'triangle';
  base: number;
  height: number;
}

function getArea(shape: Shape): number {
  switch (shape.kind) {
    case 'circle':
      return Math.PI * shape.radius ** 2;
    case 'square':
      return shape.sideLength ** 2;
    case 'triangle':
      return (shape.base * shape.height) / 2;
    default:
      const exhaustiveCheck: never = shape;
      return exhaustiveCheck;
  }
}
Code language: PHP (php)

This pattern ensures that if we add a new shape type but forget to handle it in getArea, TypeScript will give us a compilation error.

Never in Union Types

The never type has interesting behavior in union types. Since it represents values that never occur, it effectively disappears from union types:

type Combined = string | never; // Type is just string

type Extracted = Extract<string | number, never>; // Type is never
Code language: JavaScript (javascript)

Practical Applications

Error Handling

The never type is excellent for creating type-safe error handling utilities:

function assertNever(value: never): never {
  throw new Error(`Unexpected value: ${JSON.stringify(value)}`);
}

type Result<T> = Success<T> | Failure;

interface Success<T> {
  kind: 'success';
  value: T;
}

interface Failure {
  kind: 'failure';
  error: Error;
}

function handleResult(result: Result<string>): string {
  switch (result.kind) {
    case 'success':
      return result.value;
    case 'failure':
      throw result.error;
    default:
      return assertNever(result);
  }
}
Code language: JavaScript (javascript)

Type Guards with Exhaustive Checking

Combining never with type guards creates powerful type-safe patterns:

type UserRole = 'admin' | 'user' | 'guest';

function getPermissions(role: UserRole): string[] {
  switch (role) {
    case 'admin':
      return ['read', 'write', 'delete'];
    case 'user':
      return ['read', 'write'];
    case 'guest':
      return ['read'];
    default:
      const exhaustiveCheck: never = role;
      return exhaustiveCheck;
  }
}

Best Practices

  1. Use never for Exhaustive Checks
    Always include exhaustive checks in switch statements when dealing with union types:
function ensureAllCasesHandled(value: never): never {
  throw new Error(`Unhandled case: ${value}`);
}
Code language: JavaScript (javascript)
  1. Combine with Type Guards
    Use never with type guards to create more robust type checking:
function isString(value: unknown): value is string {
  return typeof value === 'string';
}

function processValue(value: string | number): string {
  if (isString(value)) {
    return value.toUpperCase();
  } else if (typeof value === 'number') {
    return value.toString();
  } else {
    const exhaustiveCheck: never = value;
    return exhaustiveCheck;
  }
}
Code language: JavaScript (javascript)

Common Gotchas and Solutions

Forgetting Return Types

When a function throws an error, TypeScript might infer any instead of never:

// Bad
function throwError(message: string) { // Inferred return type: any
  throw new Error(message);
}

// Good
function throwError(message: string): never {
  throw new Error(message);
}
Code language: JavaScript (javascript)

Mixing never with Other Types

Be careful when mixing never with other types in unions:

type Result = string | never; // Simplifies to just string

type ComplexType = {
  data: string | never; // The never is unnecessary here
};
Code language: PHP (php)

Conclusion

The never type is a powerful tool in TypeScript’s type system that helps ensure type safety and catch potential errors at compile time. By using it effectively with exhaustive checks and type guards, you can create more robust and maintainable TypeScript applications.

For more advanced TypeScript concepts, check out our guide on TypeScript Type Guards: A Complete Guide to Runtime Type Checking or explore TypeScript Union Types vs Intersections: A Complete Guide to deepen your understanding of TypeScript’s type system.

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Share via
Copy link
Powered by Social Snap