Open In App

TypeScript Exhaustiveness checking

Last Updated : 02 Nov, 2023
Improve
Improve
Like Article
Like
Save
Share
Report

TypeScript Exhaustiveness checking refers to ensuring that all possible cases of a discriminated union have been handled in a switch statement or conditional logic. To handle all kinds of cases, TypeScript introduces exhaustiveness checking with the never type. Keeping in mind, that the never type is assignable to every type; however, no type is assignable to never (except never itself).

Syntax

function processRequest(method: HttpMethod): string {
    switch (method) {
        // Cases for handling specific values
        // ...
        default:
            const _exhaustiveCheck: never = method;
            return _exhaustiveCheck;
    }
}

Where:

  • method is the parameter of the function processRequest.
  • processRequest is the function name.
  • _exhaustiveCheck is the keyword used for handling the rest of the case which is the default.
  • :string is the return type of the function that i string type

Example 1: Suppose you have a discriminated union representing different shapes and a function to calculate the area of these shapes

Javascript




type Shape = { kind: 'circle'; radius: number } | 
    { kind: 'rectangle'; width: number; height: number };
function calculateArea(shape: Shape): number {
    switch (shape.kind) {
        case 'circle':
            return Math.PI * shape.radius ** 2;
        case 'rectangle':
            return shape.width * shape.height;
    }
}


The code above appears correct, and TypeScript will allow it. However, there’s a problem: if you later add a new shape type (e.g., ‘triangle’) to the Shape union, you might forget to handle it in the calculateArea function. TypeScript doesn’t enforce that you cover all possible shape kinds.

To address this issue and make sure you handle all shape kinds, TypeScript introduces exhaustiveness checking with the never type.

Example 2: In the updated code, we’ve added a default case in the switch statement to handle any shape kind not explicitly covered. Inside the default case, we declare a variable _exhaustiveCheck of type never and assign shape to it. This informs TypeScript that this block should never be reached unless there’s a new, unhandled shape kind. If you add a new shape kind and forget to handle it, TypeScript will now raise an error on the line return _exhaustiveCheck;, alerting you to the oversight.

Javascript




type Shape = { kind: 'circle'; radius: number }
    | { kind: 'rectangle'; width: number; height: number };
function calculateArea(shape: Shape): number {
    switch (shape.kind) {
        case 'circle':
            return Math.PI * shape.radius ** 2;
        case 'rectangle':
            return shape.width * shape.height;
        default:
          
            // TypeScript detects that this block is 
            // reached only if 'shape.kind' is not 
            // 'circle' or 'rectangle'
            const _exhaustiveCheck: never = shape;
              
            // This line will raise a TypeScript 
            //error if a new shape kind is added
            return _exhaustiveCheck;
    }
}
  
const fig: Shape = {
    kind: 'rectangle',
    width: 3,
    height: 4
}
console.log(calculateArea(fig));


Output:

z39

Example 3: In this example, we handle each HTTP method with specific logic, and we’ve added a default case with exhaustiveness checking to ensure that we handle all possible values of HttpMethod. As you can see, when we call processRequest with valid HTTP methods (‘GET’, ‘POST’, ‘PUT’, ‘DELETE’), it returns the corresponding processing message. However, if you attempt to call it with an unsupported method like ‘PATCH’, TypeScript will raise an error, indicating that the argument is not assignable to the never type, helping you catch potential issues with missing method handling in your code.

Javascript




type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
function fun(method: HttpMethod): string {
    switch (method) {
        case 'GET':
            return 'Processing GET request';
        case 'POST':
            return 'Processing POST request';
        case 'PUT':
            return 'Processing PUT request';
        case 'DELETE':
            return 'Processing DELETE request';
        default:
            const _exhaustiveCheck: never = method;
            return _exhaustiveCheck;
    }
}
  
// Output: Processing GET request
console.log(fun('GET'));
  
// Output: Processing POST request
console.log(fun('POST'));
  
// Output: Processing PUT request
console.log(fun('PUT'));
  
// Output: Processing DELETE request
console.log(fun('DELETE'));
  
// If you add a new HTTP method without
// handling it, TypeScript will catch the error:
// const response = fun('PATCH');
// TypeScript error: Argument of type 'PATCH'
// is not assignable to parameter of type 'never'


Output:

z40

Conclusion: In this article, we learn about Exhaustiveness checking using never where Typescript handles all the cases. TypeScript provides this feature to help prevent unintended bugs that can occur when you forget to handle certain cases in your code. To handle all kinds of cases, TypeScript introduces exhaustiveness checking with the never type.



Like Article
Suggest improvement
Share your thoughts in the comments

Similar Reads