# What's new in ECMAScript 2023?

The official ECMAScript 2023 release is finalized and we already know which features are coming in the new version of the JavaScript spec

- URL: https://blog.lsantos.dev/en/whats-new-in-ecmascript-2023/
- Published: 2023-05-04
- Updated: 2026-07-16
- Section: typescript
- Tags: ecmascript, javascript, typescript, nodejs, development
- Language: en
- Author: Lucas Santos

---
Another year, another version of our beloved ECMAScript. For anyone who doesn't know, ECMA is the core spec that JavaScript is based on. It gets tweaked every year, with small changes shipping throughout the months and bigger changes over the years.

In this video I explain a bit more about the process behind shipping new JavaScript features. If you haven't watched it yet, I strongly recommend it so you can understand how the whole thing works!

![](https://www.youtube.com/watch?v=hDQu3AvvDfg)

Most of these changes live in the TC39 notes, the _technical committee #39_, which is the technical committee that evaluates and discusses the future of the language specifically. These notes are open and you can see all of them in the [committee's official repository](https://github.com/tc39/notes/tree/main/meetings/2023-03).

Just like I do with other technologies, JS is no different! In [2022](/news-js-2022/) I published an article about that year's news and even dared to [predict what would come this year](/o-futuro-do-js/), let's see if I got it right.

## Array.findLast

One of the proposals open on TC39 was the ability to flip the initial direction of a search on an Array. Today, the `find` and `findIndex` methods will always start searching an array from its first element (which is `0`, as it should be).

But in the case of sorted arrays, having the ability to search backwards, starting from the tail toward the head, is a lot faster and more efficient. And here you might argue: "Come on, can't you just reverse the array first and then use `find`?", basically yes, but no.

When we reverse the array using the `reverse` method, we run an operation on it that will invariably iterate over half of that array's items. Today this method is quite fast, the spec says it should work like this:

```js
function reverse(array) {
  let len = array.length
  let middle = Math.floor(len / 2)
  let lower = 0

  while (lower !== middle) {
    let lowerVal
    let upperVal

    let upper = len - lower - 1
    let upperP = upper.toString()
    let lowerP = lower.toString()

    let lowerExists = array.hasOwnProperty(lowerP)
    if (lowerExists) lowerVal = array[lowerP]

    let upperExists = array.hasOwnProperty(upperP)
    if (upperExists) upperVal = array[upperP]

    if (lowerExists && upperExists) {
      array[lowerP] = upperVal
      array[upperP] = lowerVal
    } else if (!lowerExists && upperExists) {
      array[lowerP] = upperVal
      delete array[upperP]
    } else if (lowerExists && !upperExists) {
      delete array[lowerP]
      array[upperP] = lowerVal
    } else {
      if (lowerExists || upperExists) throw new Error('This should never happen')
    }
    lower++
  }
  return array
}
```

> This implementation was taken directly from the ECMA262 spec, [section 23.1.3.26](https://tc39.es/ecma262/multipage/indexed-collections.html#sec-array.prototype.reverse), and turned from pseudo-code into JavaScript

As you can see, the implementation starts from the beginning and the end at the same time, swapping each position until the two pointers finally meet in the middle. You'd think it's not that fast, so let's run a test with this function:

```js
for (let i = 1; i <= 1_000_0000; i = i * 10) {
  console.log(`i: ${i}`)
  let a = [...Array(i).keys()]
  console.time('reverse')
  reverse(a)
  console.timeEnd('reverse')
}
```

Our output will look something like this (trimmed to fit):

```
i: 1 -> reverse: 0.006ms 
i: 10 -> reverse: 0.046ms 
i: 100 -> reverse: 0.392ms 
i: 1000 -> reverse: 2.335ms 
i: 10000 -> reverse: 4.026ms 
i: 100000 -> reverse: 68.4ms 
i: 1000000 -> reverse: 150.565ms 
i: 10000000 -> reverse: 1.542s 
```

Notice it's exponential. Sure, in modern implementations `reverse` is a lot more optimized and runs a lot faster, but the point stands: **we're going to have processing overhead**.

The new proposal creates two new methods, `findLast` and `findLastIndex`, that do exactly the same as the original methods but starting from the end. That way we avoid having to reverse the array. Here's an example:

```js
const isEven = (number) => number % 2 === 0;
const numbers = [1, 2, 3, 4];

// Existing method
console.log(numbers.find(isEven)); // 2
console.log(numbers.findIndex(isEven)); // 1

// new method
console.log(numbers.findLast(isEven)); // 4
console.log(numbers.findLastIndex(isEven)); // 3
```

## Using Hashbang

The grammar known as [hashbang (or shebang)](https://en.wikipedia.org/wiki/Shebang_\(Unix\)) is the well known sequence of characters that starts with `#!` at the top of a script. What it does is define which interpreter that script is going to use to run.

This is already possible today with Node through scripts like:

```js
#!/usr/bin/env/node

console.log('Hello')
```

Under the hood, your shell strips the first line and passes the rest of the file to the interpreter you selected, in this case the result of the `env node` command, which is the location of the Node.js executable.

The [proposal](https://github.com/tc39/proposal-hashbang) doesn't change the behavior, it just creates a standard for how this should be done everywhere.

## Copy-on-write Arrays

This is one of the main features, and also one of the ones I predicted would show up in JS this year, and one of the most requested. As we saw before, the `reverse` method does an _in place_ replacement, meaning it replaces the elements of the very same array that was passed in, returning the same reference. It essentially mutates the original object, and that's not cool.

To work around this kind of behavior, what we usually do is something like this:

```js
const original = [1,2,3]
const novo = [...original]
console.log(novo.reverse()) // [3, 2, 1]
console.log(original) // [1, 2, 3]
```

The proposal adds four new methods to `Array.prototype`, all of them a variation of the original `reverse`, `sort` and `splice` methods, and it also creates a new method called `with`, which returns a new array with a new element at a specific position swapped for another value, which avoids us doing _in place_ changes too, like with the well known `a[0] = 1` notation.

The new methods are called `toReversed`, `toSorted` and `toSpliced` (and, of course, `with`).

### `Array.prototype.toReversed()`

```js
const original = [1, 2, 3, 4];
const reversed = original.toReversed();

console.log(original);
// [ 1, 2, 3, 4 ]

console.log(reversed);
// [ 4, 3, 2, 1 ]
```

#### `Array.prototype.toSorted()`

```js
const original = [1, 3, 2, 4];
const sorted = original.toSorted();

console.log(original);
// [ 1, 3, 2, 4 ]

console.log(sorted);
// [ 1, 2, 3, 4 ]
```

#### `Array.prototype.toSpliced()`

```js
const original = [1, 4];
const spliced = original.toSpliced(1, 0, 2, 3);

console.log(original);
// [ 1, 4 ]

console.log(spliced);
// [ 1, 2, 3, 4 ]
```

#### `Array.prototype.with()`

```js
const original = [1, 2, 2, 4];
const withThree = original.with(2, 3);

console.log(original);
// [ 1, 2, 2, 4 ]

console.log(withThree);
// [ 1, 2, 3, 4 ]
```

## WeakMaps with Symbols

This is a [proposal](https://github.com/tc39/proposal-symbols-as-weakmap-keys) that's pretty hard to explain, if only because it's a really specific case of other pretty specific cases... But, in short, what happens is that the spec now allows [Symbols](https://medium.com/trainingcenter/javascript-symbols-decifrando-o-mist%C3%A9rio-383e359e64e3) to be used as keys for [WeakMaps](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap).

> I won't go into detail about WeakMaps here, but you can see more about real world uses in this [StackOverflow](https://stackoverflow.com/a/29416340) answer

Previously, only objects were allowed as keys, but there's another case where we have a unique value that can't be recreated with the same value, and that's `Symbols`. This proposal makes WeakMaps accept these values as keys too.

```js
const weak = new WeakMap();
const key = Symbol("ref");
weak.set(key, "ECMAScript 2023");

console.log(weak.get(key));
// ECMAScript 2023
```

## Wrapping up

A lot of what I predicted in the [previous article](/o-futuro-do-js/) ended up getting promoted to a later stage, some got dropped, some even earned their own articles, while others are exactly where they were a year ago.

This update to the spec doesn't touch much of what we use day to day, but it promises to be a big quality of life improvement for anyone using more specific features that can definitely have a big impact on efficiency and performance.
