Exhaustive checking in TypeScript using 'never' type

Created: Oct 30th 2022 - Updated : Nov 3rd 2022

In TypeScript you can do exhaustive checking with 'never'. You can make sure you cover every possible scenario and avoid forgetting one especially if you add it later in your code.

You can add a 'switch' case and make sure that everything that is outside my case should never happen

main.ts

const rateMultiplier = 1.5

type Car = {type: 'car' , rate : number}
type Motorbike = {type: 'motorbike', rate: number}
type Vehicles = Car | Motorbike

function getRate(vehicle : Vehicles) {
	switch (vehicle.type) {
    	case 'car' : 
        return vehicle.rate * rateMultiplier;
        
        case 'motorbike':
        return vehicle.rate * rateMultiplier - (vehicle.rate * discountAmount);
        
        default : 
        // This should never happen !
        let shouldNeverHappen : never = vehicle;
        return shouldNeverHappen;
    }
}

Let's say a developer comes back at a later stage and adds a new vehicle type in without adding it to the switch statement. In that case the dev will be informed by TypeScript that the new vehicle type was not accounted for in the switch statement

main.ts

const rateMultiplier = 1.5

type Car = {type: 'car' , rate : number}
type Motorbike = {type: 'motorbike', rate: number}
type Bicycle = {type: 'bicycle', rate: number} // We added Bicycle here

type Vehicles = Car | Motorbike | Bicycle // and here

function getRate(vehicle : Vehicles) {
	switch (vehicle.type) {
    	case 'car' : 
        return vehicle.rate * rateMultiplier;
        
        case 'motorbike':
        return vehicle.rate * rateMultiplier - (vehicle.rate * discountAmount);
        
        default : 
        // This should never happen !
        let shouldNeverHappen : never = vehicle;
        return shouldNeverHappen; // Type 'Bicycle' is not assignable to type 'never'.
    }
}

As you can see above, TypeScript detects the possibility of Bicycle being used, but since it was not included in the switch statement, the default part is now reached, and it is of type 'never'. That returns a TypeScript Error reminding us that we need to include 'bicycle' within our switch statement. (and anywhere where it could be called in our codebase)

Another example of a case where this could be useful

let's say we have an article with sizes.

item-size-calc.ts

type SizeOptions = 's' | 'm' | 'l' | 'xl' 


function getSizePriceVariant(size : SizeOptions) {
	 if (size === 's' || size === 'm') {
    	return 1;
    } else if (size === 'l' || size === 'xl') {
    	return 1.2;
    }
    
    // This code should be unreachable
    const neverHappens: never = size;
    return neverHappens;
}

Again, if a developer adds another size options but without accounting for it in the place this is used in your codebase, an error will pop and the dev will be forced to adapt the function to make it compatible with the typing.

item-size-calc.ts

type SizeOptions = 's' | 'm' | 'l' | 'xl' | 'xxl'


function getSizePriceVariant(size : SizeOptions) {
	 if (size === 's' || size === 'm') {
    	return 1;
    } else if (size === 'l' || size === 'xl') {
    	return 1.2;
    }
    
    // This code should be unreachable
    const neverHappens: never = size; // Type 'string'  is not assignable to type 'never'
    return neverHappens;
}

Here you go, you now know how to protect your code from situations like this using TypeScript and leverage 'never' to have red flags raised in your code whenever you, or someone else, does a modification and forgets to do exhaustive checking somewhere else in the code.