Pattern Matching in JavaScript: A First Look at the TC39 Proposal
Explore the TC39 pattern matching proposal that brings switch-like syntax with destructuring, guards, and exhaustiveness.
Why Pattern Matching?
JavaScript's \switch\ statement is limited: it only matches by value equality, falls through by default (a common source of bugs), and can't destructure or match complex shapes. The pattern matching proposal introduces \match\ — a powerful expression that combines matching, destructuring, and guards in one clean syntax.
Basic Syntax
``javascript
// Current switch — limited and error-prone
switch (response.status) {
case 200: handleSuccess(response); break;
case 404: handleNotFound(); break;
case 500: handleError(); break;
default: handleUnknown(response);
}
// Pattern matching — expressive and safe
const result = match (response) {
when ({ status: 200, body }) => processBody(body),
when ({ status: 404 }) => 'Not Found',
when ({ status: 500, error }) => \Server Error: \${error}\,
when ({ status }) if (status >= 300 && status < 400) => 'Redirect',
default => 'Unknown response'
};
`
Destructuring Patterns
Pattern matching integrates deep destructuring — you can match and extract nested values in one step.
`javascript
const describe = (value) => match (value) {
when (null) => 'null value',
when (undefined) => 'undefined value',
when ({ type: 'circle', radius }) if (radius > 0) =>
\Circle with area \${Math.PI * radius ** 2}\,
when ({ type: 'rect', width, height }) =>
\Rectangle: \${width}x\${height}\,
when ([first, ...rest]) =>
\Array starting with \${first}, \${rest.length} more\,
when (Number) if (value > 0) => 'Positive number',
when (String) => \String: "\${value}"\,
default => 'Something else'
};
describe({ type: 'circle', radius: 5 }); // "Circle with area 78.53..."
describe([1, 2, 3]); // "Array starting with 1, 2 more"
describe('hello'); // 'String: "hello"'
`
Real-World Use Cases: Redux Reducers
`javascript
function reducer(state, action) {
return match (action) {
when ({ type: 'ADD_TODO', payload: { text, id } }) => ({
...state,
todos: [...state.todos, { id, text, done: false }]
}),
when ({ type: 'TOGGLE_TODO', payload: { id } }) => ({
...state,
todos: state.todos.map(t =>
t.id === id ? { ...t, done: !t.done } : t
)
}),
when ({ type: 'DELETE_TODO', payload: { id } }) => ({
...state,
todos: state.todos.filter(t => t.id !== id)
}),
default => state
};
}
`
Simulating Pattern Matching Today
While waiting for the proposal, you can approximate the pattern:
`javascript
function match(value, patterns) {
for (const [predicate, handler] of patterns) {
if (predicate(value)) return handler(value);
}
throw new Error('No pattern matched');
}
const result = match(response, [
[r => r.status === 200, r => processBody(r.body)],
[r => r.status === 404, () => 'Not Found'],
[r => r.status >= 500, r => \Error: \${r.error}\],
[() => true, () => 'Unknown'],
]);
``
Pattern matching will make JavaScript code more declarative, less error-prone, and more expressive. It's especially powerful for handling complex state transitions, API responses, and data transformations.