0
0
Typescriptprogramming~15 mins

Exhaustive checking with never in Typescript - Deep Dive

Choose your learning style9 modes available
Overview - Exhaustive checking with never
What is it?
Exhaustive checking with never is a TypeScript technique that ensures all possible cases in a code block are handled. It uses the special type 'never' to catch missing cases at compile time. This helps prevent bugs by making sure no scenario is forgotten. It is often used with switch statements or conditional logic on union types.
Why it matters
Without exhaustive checking, some cases might be missed, causing unexpected behavior or runtime errors. This technique forces developers to consider every possible input, improving code safety and reliability. It saves time and frustration by catching errors early during development rather than after deployment.
Where it fits
Learners should know basic TypeScript types, especially union types and type narrowing. After this, they can explore advanced type safety patterns and error handling. This concept fits into writing robust, maintainable TypeScript code.
Mental Model
Core Idea
Exhaustive checking with never means making TypeScript prove that every possible case is handled, so no surprises happen at runtime.
Think of it like...
It's like checking off every item on a grocery list before leaving the store to make sure nothing important is forgotten.
┌───────────────┐
│  Union Type   │
│ (A | B | C)   │
└──────┬────────┘
       │
       ▼
┌───────────────┐
│  switch/case  │
│  on A, B, C   │
└──────┬────────┘
       │
       ▼
┌─────────────────────────────┐
│  default: assign to never    │
│  triggers error if reached   │
└─────────────────────────────┘
Build-Up - 6 Steps
1
FoundationUnderstanding union types basics
🤔
Concept: Union types allow a variable to hold one of several types.
In TypeScript, you can write a type that is one of many options using the | symbol. For example, type Fruit = 'apple' | 'banana' | 'orange'; means a variable of type Fruit can be any of those three strings.
Result
You can assign 'apple', 'banana', or 'orange' to a Fruit variable, but not 'pear'.
Knowing union types is essential because exhaustive checking works by ensuring all union options are handled.
2
FoundationType narrowing with switch statements
🤔
Concept: Switch statements can narrow a union type to specific cases.
When you use a switch on a union type variable, TypeScript understands which case matches which type. For example, switching on a Fruit variable lets you write code for 'apple', 'banana', and 'orange' separately.
Result
Inside each case, TypeScript treats the variable as the narrowed type, allowing safe operations.
Type narrowing lets you write different logic for each union member, setting the stage for exhaustive checks.
3
IntermediateIntroducing the never type
🤔
Concept: The never type represents values that never occur.
Never is a special TypeScript type that means 'this should never happen'. For example, a function that always throws an error returns never because it never returns normally.
Result
You cannot assign any value to a never type, and it signals unreachable code.
Understanding never is key because it helps detect impossible or unhandled cases.
4
IntermediateUsing never for exhaustive checks
🤔Before reading on: do you think TypeScript will catch missing cases automatically without never? Commit to your answer.
Concept: Assigning unhandled cases to never forces TypeScript to check all cases are covered.
In a switch on a union type, add a default case that assigns the variable to a never-typed variable. If any case is missing, TypeScript will error because the variable is not actually never.
Result
If you forget a case, TypeScript shows a compile error, preventing runtime bugs.
Using never in this way turns TypeScript into a safety net that guarantees all cases are handled.
5
AdvancedPattern for exhaustive switch statements
🤔Before reading on: do you think the default case with never can be omitted safely? Commit to your answer.
Concept: A common pattern uses a helper function to assert never, improving error messages and code clarity.
Define a function like function assertNever(x: never): never { throw new Error('Unexpected value: ' + x); } Then call it in the default case with the variable. This makes errors clearer and documents intent.
Result
The code fails to compile if any case is missing, and runtime errors are descriptive if something unexpected happens.
This pattern is a best practice that improves maintainability and debugging.
6
ExpertExhaustive checking with discriminated unions
🤔Before reading on: do you think exhaustive checking works the same with object unions as with string unions? Commit to your answer.
Concept: Discriminated unions use a common property to distinguish types, enabling exhaustive checks on complex objects.
For example, type Shape = { kind: 'circle', radius: number } | { kind: 'square', size: number }; You can switch on shape.kind and use never to ensure all shapes are handled.
Result
This technique scales exhaustive checking to real-world data structures, not just simple strings.
Knowing how to apply never with discriminated unions unlocks powerful type safety in complex applications.
Under the Hood
TypeScript's compiler uses control flow analysis to track which union members are handled in code branches. When a variable is assigned to never, the compiler checks if that assignment is possible. If it is, it means some cases are unhandled, so it raises a compile error. This leverages the type system to enforce completeness without runtime overhead.
Why designed this way?
TypeScript was designed to add static safety to JavaScript without changing runtime behavior. Using never for exhaustive checks fits this goal by catching errors early during development. Alternatives like runtime checks add overhead and complexity, so compile-time checks are preferred.
┌───────────────┐
│ Union Type    │
│ (A | B | C)   │
└──────┬────────┘
       │
       ▼
┌───────────────┐
│ switch on type│
│ cases A, B    │
└──────┬────────┘
       │
       ▼
┌─────────────────────────────┐
│ default: assign to never var │
│ (compile error if reached)   │
└─────────────────────────────┘
Myth Busters - 3 Common Misconceptions
Quick: Does TypeScript automatically check all union cases without using never? Commit to yes or no.
Common Belief:TypeScript always forces you to handle all union cases without extra code.
Tap to reveal reality
Reality:TypeScript only warns about missing cases if you use never in the default case; otherwise, missing cases can go unnoticed.
Why it matters:Without never, bugs from unhandled cases can slip into production, causing unexpected behavior.
Quick: Is the never type just another regular type you can assign values to? Commit to yes or no.
Common Belief:Never is just a fancy type like string or number.
Tap to reveal reality
Reality:Never represents impossible values and cannot hold any value; it signals unreachable code.
Why it matters:Misunderstanding never leads to incorrect type assertions and missed compile-time errors.
Quick: Can exhaustive checking with never catch runtime errors caused by dynamic data? Commit to yes or no.
Common Belief:Exhaustive checking with never guarantees no runtime errors from unhandled cases.
Tap to reveal reality
Reality:It only guarantees compile-time safety; runtime errors can still occur if data is malformed or cast unsafely.
Why it matters:Relying solely on never can give false confidence; runtime validation is still important.
Expert Zone
1
Exhaustive checking works best with discriminated unions where a common property clearly identifies each type.
2
Using assertNever helper functions improves error messages and documents developer intent explicitly.
3
TypeScript's control flow analysis can sometimes be bypassed by complex code patterns, requiring manual checks.
When NOT to use
Avoid exhaustive checking with never in very dynamic or loosely typed code where union types are not well defined. Instead, use runtime validation libraries or schema checks for safety.
Production Patterns
In production, exhaustive checking is used in reducers, parsers, and state machines to ensure all states or actions are handled. It is combined with unit tests and runtime checks for robust systems.
Connections
Pattern Matching (Functional Programming)
Exhaustive checking with never is a TypeScript way to enforce pattern matching completeness.
Understanding exhaustive checks helps grasp how functional languages ensure all cases in pattern matching are handled, improving code safety.
Formal Logic - Proof by Exhaustion
Both involve checking all possible cases to prove a statement or correctness.
Knowing this connection shows how programming safety techniques mirror logical proof methods, reinforcing rigorous thinking.
Quality Control in Manufacturing
Exhaustive checking is like inspecting every product variant to ensure no defects slip through.
This cross-domain link highlights how thoroughness in one field (manufacturing) parallels software safety practices.
Common Pitfalls
#1Forgetting to add the default case with never in a switch statement.
Wrong approach:switch (fruit) { case 'apple': // handle apple break; case 'banana': // handle banana break; // missing orange case and no default }
Correct approach:switch (fruit) { case 'apple': // handle apple break; case 'banana': // handle banana break; case 'orange': // handle orange break; default: const _exhaustiveCheck: never = fruit; return _exhaustiveCheck; }
Root cause:Not knowing that the default case with never triggers compile errors for missing cases.
#2Assigning a value to a never variable directly.
Wrong approach:let x: never = 'some value';
Correct approach:let x: never; // x cannot be assigned any value
Root cause:Misunderstanding that never means no possible value can exist.
#3Assuming exhaustive checking prevents all runtime errors.
Wrong approach:function process(input: unknown) { // rely only on exhaustive checks without runtime validation }
Correct approach:function process(input: unknown) { if (isValid(input)) { // safe to process } else { throw new Error('Invalid input'); } }
Root cause:Confusing compile-time type safety with runtime data validation.
Key Takeaways
Exhaustive checking with never ensures all possible cases in union types are handled, preventing bugs.
The never type represents impossible values and is used to catch unhandled cases at compile time.
Adding a default case that assigns to never triggers TypeScript errors if any case is missing.
Using helper functions like assertNever improves code clarity and error messages.
Exhaustive checking complements but does not replace runtime validation for safe programs.