---
# System prepended metadata

title: ECMAScript Simple Pattern Matching Proposal

---


Problems with the existing pattern matching proposal:

- Doesn't interact with existing patterns. Invents new pattern syntax with completely different semantics.
- Adds separate constructs which use only the new pattern syntax while leaving existing ones without the new featurs.
- Can't be split into separate proposals. Most of features are tightly coupled with each other.
- Probably is never gonna get accepted because it's so big.

# ECMAScript Simple Pattern Matching Proposal

Let's add pattern matching to ECMAScript by introducing a few small features and syntax additions.
Every feature presented here adds a bit of useful functionality which can be used on its own and **can even be split into a separate proposal**.

The general idea is to

1. Take existing patterns (let/const bindings, function parameters, etc) and expand their capabilities by allowing new kinds of pattern syntax.
2. Add a new syntax to the `switch` statement which matches using any pattern (in addition to the existing `case` statement which matches by a single value)

## Already existing pattern syntax

There's already some pattern syntax which asserts the type of a value and otherwise throws an error.
This syntax would work seamlessly with new `switch` additions. Consistency!

### Object pattern

Asserts that the value is an object, otherwise throws an error.

```js
const { a, b: c, d = 5, ...rest } = obj;
```

### Array/iterable pattern

Asserts that the value is iterable, otherwise throws an error.

```js
const [a, b, c = 3, ...rest] = arr;
```

## New pattern syntax

The following are additions to the pattern syntax.
They will work in existing places where patterns are used and in the new `switch` syntax.

### Literals in patterns

Allow literals in patterns which act like a value assertion.
If the matched value isn't equal to the one provided in the pattern an error is thrown.
Allowed are string, number and boolean values and null.

```js
// Throws an error if the response was not ok or status wasn't 200
const { ok: true, status: 200, statusText } = await fetch("/");

// Throws an error if the first segment of path isn't "root"
const ["root", ...rest] = path.split("/");

// Simply assert that status is "success", otherwise throw
const "success" = status
```

> TODO: currently an `undefined` literal would instead try to bind the value to a variable named `undefined`.
> It fails in most cases, but works in function parameters.
> Possible solution: keyword `void` matches `undefined`.

### Not-undefined pattern

Checking for property existence is one of the most common applications of pattern matching.
Unfortunately the current behavior of patterns is that when a property is missing, the whole pattern still matches and the binding is set to undefined.
This necessitates a syntax which would change that behavior.
It should be concise since it probably will be used often.

Let's add a pattern which asserts that the value is not undefined.
The syntax shall be a unary postfix `!` after a sub-pattern.

It changes the subpattern so that it only matches if the matched value is not undefined.
In other words, it adds an additional requirement to the pattern, so it works kinda like and-pattern automatically (see below).

A shorthand for destructured object properties shall be supported.

```js
// Throws an error if the function isn't called with a user ID
function getUser(userId!) {
  // Will throw early if user's name or age is missing.
  const { name: firstName!, age! } = users[userId];
}
```

### Or pattern

Add a pattern which matches if at least one of the sub-patterns match.
If one subpattern matches, the rest aren't evaluated.
If none of the sub-patterns match, an AggregateError is thrown.

The syntax shall be a binary `|` operator where operands are sub-patterns.

If a variable binding exists in only one of the sub-patterns, it will be declared, but will be undefined if other sub-pattern matches.

If the same iterable is iterated in multiple sub-patterns, it will be iterated only once.
The result of the iteration will be cashed and reused in the other sub-patterns.

```js
// Throws an error if the response wasn't successful.
const { status: 200 | 201 } = await fetch("/");

// Retrieves user's name from either first or last name property.
// Throws if neither is defined.
const { firstName: name! } | { lastName: name! } = user;
```

## New `switch` syntax

Add new syntax to the `switch` statement to make use of the pattern syntax.

### `const` and `let` cases

Allow `const` and `let` in addition to `case` and `default` in `switch` statements.

The statement matches if the given pattern doesn't result in one of the errors described above.
Only the errors originating from failed patterns are caught and treated as failed match.
If, for example, an object getter throws a custom exception during match, it won't be caught automatically.

If an iterable is iterated in multiple cases, it will be iterated only once and it's results will be cached and reused in the other cases.

```js
switch (response) {
  const { ok: true }: 
    console.log(await response.json());
    break;
  const { ok: false, status, statusText }: 
    console.error(`Error: ${status} ${statusText}`);
    break;
}
```

> TODO: is this distinguishable from a regular const or let statement? Maybe add a keyword: `case const` and `case let`?

### `break`less cases

Add a syntax similar to an arrow function which allows omitting the `break` statement.
The syntax shall use thin arrow (`->`) to distinguish it from a function syntax, but also to suggest similar usage.

```js
switch (payload) {
  const { type: "add", value } -> state.push(value);
  const { type: "remove", index } -> state.splice(index, 1);
  const { type: "update", index, value } -> {
    state[index] = value;
  }
}
```

> TODO: Can semicolons be optional?

### `switch` as an expression

Either wait until the [do-expressions proposal](https://github.com/tc39/proposal-do-expressions) is implemented or propose a similar feature just for switch. The syntax should align with the do-expressions proposal.

Let's assume that all you have to do is put a switch in expression position to enable this behavior.

```jsx
return (
  <div>
    {switch (request) {
      const { status: "loading" } -> <Spinner />;
      const { status: "success", data } -> <UserProfile data={data} />;
      const { status: "error", error } -> <Alert message={error} />;
    }}
  </div>
);
```

## Summary

These additions encompass most of the features of the popular [`ts-pattern`](https://github.com/gvergnaud/ts-pattern) library.
One advantage of bringing these features to the language is that they additionally allow control flow (like `break` and `return`) to work from inside the match branches.

## Future improvements

These are just ideas which could be added in the future proposals or not.

### And pattern

Add a pattern which matches if all of the sub-patterns match.
If at least one of the sub-patterns fails, the whole pattern fails and throws an error.

```js
// Asserts that the options parameter is an object
function init(options & {}) {}

// Asserts that the array has exactly 2 elements and binds them to x and y
const { length: 2 } & [x, y] = point;
```

### `let` and `const` in `if` and `while` statements

Allow `let` and `const` in `if` and `while` statements to test patterns and bind variables at the same time.

[Similar feature](https://doc.rust-lang.org/reference/expressions/if-expr.html#if-let-expressions) exists in Rust.

```js
if (const { status: 200, statusText } = await fetch("/logout", { method: "POST" })) {
  console.log("Success:", statusText);
}
```

> TODO: What about `if (let pat = expr && let pat = expr)`?
> Rust doesn't support it neither, but makes it a syntax error for future compatibility.

### Pattern guards

Allow syntax at the end of any pattern which allows to add any condition to the pattern.
The syntax shall be `pattern if (expr)`.

[Similar feature](https://doc.rust-lang.org/reference/expressions/match-expr.html#match-guards) exists in Rust.

```js
switch (response) {
  const { status } if (status >= 200 && status < 300) -> console.log("Success");
  const { status } if (status >= 400 && status < 500) -> console.warn("Client error");
  default -> console.error("Unexpected response");
}
```

TODO: Should it be allowed in `if-let` and `if-const` as well? How about regular `let` and `const`?

### `typeof` pattern

A pattern which allows matching on value's type.
The syntax shall be a prefix `typeof` operator followed by a sub-pattern which matches the string value representing the type: `typeof <pattern>`

```js
switch (value) {
  const typeof "string" -> console.log("It's a string");
  const typeof "number" -> console.log("It's a number");
  default -> console.log("It's something else");
}

function add(a & typeof "number", b & typeof "number") {
  return a + b;
}

// Assigns `typeof value` to `valueType` in a weird way
const typeof valueType = value
```

### `instanceof` pattern

A pattern which asserts that the value is an instance of a given class.
The syntax shall be a prefix `instanceof` operator followed by an expression which evaluates to a class: `instanceof <expr>`.

```js
try {
  doSomething()
} 
catch (error) {
  switch (error) {
    const instanceof TypeError & { message } -> console.error("Type error:", message);
    const instanceof RangeError & { message } -> console.error("Range error:", message);
    default -> {
      throw error;
    }
  }
}
```

### Comparison patterns

A pattern which asserts that the value is greater, less or equal to a given value.
The syntax shall be a prefix comparison operator followed by an expression. E.g. `<= 123`.

```js
switch (response.status) {
  const >= 500 -> console.error("Server error");
  const >= 400 -> console.warn("Client error");
  default -> console.log("Success");
}

const { length: > 0 & <= 10 } = array;
```

### Template literal pattern

A pattern which asserts that the value is a string which matches a given template literal.
The holes in the literal are sub-patterns.

```js
const `${user}@${domain}` = email;
```

```js
switch (userCommand) {
  const `/kick ${userId}` -> kickUser(userId);
  const `/ban ${userId}` -> banUser(userId);
}
```

### RegExp pattern

A pattern which asserts that the value is a string which matches a given regular expression.

```js
switch (s) {
  const /.*@.*\..*/ -> console.log("It's an email");
  const /.*:\/\/.*/ -> console.log("It's a URL");
}
```

### Let-else and const-else statements

Add a syntax to `let` and `const` which allows to do something else than throwing a default error.
Variable declarations can be followed by an `else` branch which runs when the pattern doesn't match.
The `else` branch can be a single diverging statement or a block which ends with a diverging statement (`return`, `break`, `continue` or `throw`).

[Similar feature](https://doc.rust-lang.org/reference/statements.html#let-statements) exists in Rust.

```js
for (const child of element.children) {
  const { tagName: "a", href } = child else continue;
  console.log("Link:", href);
}
```

### Exact length iterable pattern

Default array/iterable pattern currently only matches on the specified starting elements and ignores the rest.
There could be a syntax which makes the pattern fail if there are more elements than expected.

```js
// Must have at least 1 element and no more than 3
const [a!, b, c, ...void] = generator();
```


### Custom matchers

Add `@expr` syntax which evaluates expr and calls it's `Symbol.match` method with the value. If it throws an error, the pattern fails.

```js
if (const { length: @range(0, 10) } = str) {}
```

> If the match method returns a value, it could be matched by a sub-pattern. Syntax is TODO.