Exhaustive checking with never in Typescript - Time & Space Complexity
We want to see how the time needed grows when using exhaustive checks with the never type in TypeScript.
How does the program behave as the number of cases to check increases?
Analyze the time complexity of the following code snippet.
type Shape = 'circle' | 'square' | 'triangle';
function area(shape: Shape, size: number): number {
switch (shape) {
case 'circle':
return Math.PI * size * size;
case 'square':
return size * size;
case 'triangle':
return (Math.sqrt(3) / 4) * size * size;
default:
const _exhaustiveCheck: never = shape;
return _exhaustiveCheck;
}
}
This code calculates area for different shapes and uses never to ensure all cases are handled.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: The
switchstatement checks the shape once per call. - How many times: Exactly one check per function call; no loops or recursion.
Explain the growth pattern intuitively.
| Input Size (n) | Approx. Operations |
|---|---|
| 3 (shapes) | Up to 3 checks per call |
| 10 (hypothetical shapes) | Up to 10 checks per call |
| 100 (hypothetical shapes) | Up to 100 checks per call |
Pattern observation: The number of checks grows linearly with the number of shape cases.
Time Complexity: O(n)
This means the time to check grows directly with the number of shape cases you have.
[X] Wrong: "Using never makes the code run faster or skip checks."
[OK] Correct: The never type is only for compile-time checks. At runtime, the switch still checks each case one by one.
Understanding how exhaustive checks affect runtime helps you write safer code and explain your reasoning clearly in interviews.
"What if we replaced the switch with a dictionary lookup? How would the time complexity change?"
Practice
never in exhaustive checking in TypeScript?Solution
Step 1: Understand the role of
neverin exhaustive checksneveris used to represent values that should never occur, helping catch unhandled cases.Step 2: Connect
By forcing a function to acceptneverusage to exhaustive case handlingnever, TypeScript ensures all cases are covered, or else a compile error occurs.Final Answer:
To ensure all possible cases are handled in a switch or conditional -> Option DQuick Check:
Exhaustive checking = catch all cases [OK]
- Thinking never allows any value
- Using never to ignore errors
- Confusing never with unknown type
never to enforce exhaustive checking?Solution
Step 1: Identify correct parameter and return types
The helper function must accept a parameter of typeneverand returnneverbecause it throws an error.Step 2: Check function body matches expected behavior
The function throws an error with the unexpected value, which is the standard pattern for exhaustive checks.Final Answer:
function assertNever(x: never): never { throw new Error('Unexpected value: ' + x); } -> Option AQuick Check:
Helper function with never param and throw = correct [OK]
- Using any or unknown instead of never
- Not throwing an error inside the function
- Returning void instead of never
type Shape = 'circle' | 'square';
function area(shape: Shape) {
switch (shape) {
case 'circle':
return 3.14 * 2 * 2;
// Missing case for 'square'
default:
return assertNever(shape);
}
}
function assertNever(x: never): never {
throw new Error('Unexpected shape: ' + x);
}
console.log(area('square'));Solution
Step 1: Analyze switch cases and input
The function handles 'circle' but not 'square', so at runtime, calling area('square') hits the default case.Step 2: Understand assertNever behavior at runtime
The assertNever function throws an error with the unexpected shape, so the program throws a runtime error.Final Answer:
Throws Error: Unexpected shape: square -> Option BQuick Check:
Missing case causes runtime error when called [OK]
- Assuming missing case returns undefined
- Thinking it returns a number for all inputs
- Expecting compile error instead of runtime error
type Direction = 'up' | 'down' | 'left';
function move(dir: Direction) {
switch (dir) {
case 'up':
return 'Moving up';
case 'down':
return 'Moving down';
default:
return assertNever(dir);
}
}
function assertNever(x: never): never {
throw new Error('Unexpected direction: ' + x);
}Solution
Step 1: Check all union cases in switch
The type Direction has 'up', 'down', and 'left', but 'left' is missing in the switch.Step 2: Understand impact of missing case with assertNever
Missing 'left' causes default to call assertNever with a string, which is not never, causing a compile error.Final Answer:
Missing case for 'left' in switch statement -> Option AQuick Check:
All union cases must be handled [OK]
- Ignoring missing union cases
- Thinking default case covers all safely
- Wrong assertNever parameter type
Status values?type Status = 'success' | 'error' | 'loading';
function handleStatus(status: Status) {
switch (status) {
case 'success':
return 'Done';
case 'error':
return 'Failed';
// Missing 'loading' case
default:
return assertNever(status);
}
}
function assertNever(x: never): never {
throw new Error('Unexpected status: ' + x);
}Solution
Step 1: Identify missing union case in switch
The 'loading' case is missing, so the switch is not exhaustive.Step 2: Add missing case to ensure all Status values are handled
Addingcase 'loading':with appropriate return ensures exhaustive checking and no runtime errors.Final Answer:
Add a case for 'loading' in the switch statement -> Option CQuick Check:
All union members must have cases [OK]
- Removing default case without adding missing cases
- Changing type to avoid handling all cases
- Using any instead of never in assertNever
Ready to prove it?
Pass the test and this pattern moves unknown
