Exhaustive pattern matching in Typescript - Time & Space Complexity
We want to understand how the time it takes to run exhaustive pattern matching changes as the number of cases grows.
How does checking all possible patterns affect the program's speed?
Analyze the time complexity of the following code snippet.
function describeValue(value: string | number | boolean) {
return match(value) {
case (v: string) => `String: ${v}`,
case (v: number) => `Number: ${v}`,
case (v: boolean) => `Boolean: ${v}`,
};
}
This code checks the type of the input and returns a description for each possible type.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: Checking each pattern case one by one.
- How many times: Once per case until a match is found.
As the number of patterns increases, the program checks more cases in order.
| Input Size (number of cases) | Approx. Operations (checks) |
|---|---|
| 3 | Up to 3 checks |
| 10 | Up to 10 checks |
| 100 | Up to 100 checks |
Pattern observation: The number of checks grows directly with the number of cases.
Time Complexity: O(n)
This means the time to find a matching case grows linearly with the number of patterns.
[X] Wrong: "Exhaustive pattern matching checks all cases every time, no matter what."
[OK] Correct: The matching stops as soon as a case fits, so it often checks fewer cases than the total.
Understanding how pattern matching scales helps you explain code efficiency clearly and shows you can reason about control flow in real projects.
"What if the patterns were nested or involved recursive structures? How would that affect the time complexity?"
Practice
What is the main purpose of exhaustive pattern matching in TypeScript?
Solution
Step 1: Understand union types and pattern matching
Exhaustive pattern matching ensures every variant of a union type is explicitly handled in code.Step 2: Identify the purpose of exhaustive checks
This prevents missing cases that could cause bugs or unexpected behavior.Final Answer:
To ensure all possible cases of a union type are handled -> Option BQuick Check:
Exhaustive pattern matching = handle all cases [OK]
- Thinking it improves runtime speed
- Confusing with dynamic typing
- Assuming it auto-generates code
Which of the following switch statements correctly uses exhaustive pattern matching for this union type?
type Shape = 'circle' | 'square' | 'triangle';
Solution
Step 1: Check all cases are handled
Cases for 'circle', 'square', and 'triangle' are present in options B, C, and D.Step 2: Verify exhaustive check with never type
Only switch(shape) { case 'circle': break; case 'square': break; case 'triangle': break; default: const _exhaustiveCheck: never = shape; } usesdefaultwithconst _exhaustiveCheck: never = shape;to enforce exhaustiveness.Final Answer:
switch with all cases and default assigning to never -> Option CQuick Check:
Exhaustive switch = all cases + never default [OK]
- Omitting the default case with never assignment
- Using default without never type
- Missing one of the union cases
What will be the output of this TypeScript code?
type Status = 'success' | 'error';
function handleStatus(status: Status) {
switch (status) {
case 'success':
return 'Yay!';
case 'error':
return 'Oops!';
default:
const _exhaustiveCheck: never = status;
return _exhaustiveCheck;
}
}
console.log(handleStatus('success'));Solution
Step 1: Analyze the switch cases for 'success'
The input is 'success', so the first case matches and returns 'Yay!'.Step 2: Check the default case usage
The default case is unreachable here but ensures exhaustiveness by assigning to never.Final Answer:
"Yay!" -> Option DQuick Check:
Input 'success' returns 'Yay!' [OK]
- Thinking default case runs for valid inputs
- Expecting runtime or compile errors
- Confusing return values for cases
Identify the error in this exhaustive pattern matching code:
type Direction = 'up' | 'down' | 'left' | 'right';
function move(dir: Direction) {
switch (dir) {
case 'up':
return 'Moving up';
case 'down':
return 'Moving down';
case 'left':
return 'Moving left';
default:
const _exhaustiveCheck: never = dir;
return _exhaustiveCheck;
}
}Solution
Step 1: Check all union cases in switch
Cases for 'up', 'down', and 'left' exist, but 'right' is missing.Step 2: Understand the role of default with never
The default assigns to never to catch unhandled cases, so missing 'right' causes a compile error.Final Answer:
Missing case for 'right' in the switch statement -> Option AQuick Check:
All union cases must be handled explicitly [OK]
- Assuming default covers missing cases silently
- Misusing never assignment in default
- Ignoring missing union members
Given this nested union type, which switch structure correctly uses exhaustive pattern matching to handle all cases?
type Animal = { type: 'dog'; breed: 'bulldog' | 'beagle' } | { type: 'cat'; color: 'black' | 'white' };Solution
Step 1: Check outer switch covers all 'type' cases
Cases for 'dog' and 'cat' are present with default assigning to never.Step 2: Check inner switches cover all nested union cases
For 'dog', both 'bulldog' and 'beagle' are handled with default never; for 'cat', both 'black' and 'white' are handled similarly.Step 3: Verify no missing cases in nested switches
All nested union members are covered with exhaustive checks.Final Answer:
Nested switches with all cases and never defaults for both levels -> Option AQuick Check:
Nested exhaustive switches = all cases + never defaults [OK]
- Skipping some nested union cases
- Using if conditions without exhaustive checks
- Missing never assignment in inner switches
Ready to prove it?
Pass the test and this pattern moves unknown
