What are the ES2023 array methods toSorted, toReversed, toSpliced and with, and how do they differ from their older counterparts?
answer
- four names added in ES2023
- the copying twins of the in-place four
- prefix on three, one is a preposition
- toSorted, toReversed, toSpliced, with
- toSpliced returns the result, not the removals
basics
~20 sES2023 added four copying array methods — toSorted, toReversed, toSpliced and with — that return a new array instead of editing the receiver, giving non-mutating counterparts to sort, reverse, splice and assignment to an index.
solid answer
~40 sThey are the "change array by copy" methods from ES2023. `toSorted()` mirrors `sort()`, `toReversed()` mirrors `reverse()`, `toSpliced(start, skipCount, ...items)` mirrors `splice()`, and `with(index, value)` replaces the copying equivalent of `arr[index] = value`. Each takes the same arguments as its in-place twin but returns a brand-new array and leaves the receiver untouched, so you can write `const sorted = data.toSorted(byName)` without the old `data.slice().sort(byName)` dance. Two details worth knowing: `toSpliced` returns the *resulting* array while `splice` returns the removed elements, and `with` throws a `RangeError` if the index is out of bounds (negative indexes are allowed and count from the end). The copies are shallow, and the methods produce dense arrays, so holes in a sparse source come out as `undefined`. They are supported in modern browsers and Node 20 or later.
code
javascript · 13 linesconst scores = [30, 10, 20];
console.log(scores.toSorted((a, b) => a - b)); // [10, 20, 30]
console.log(scores.toReversed()); // [20, 10, 30]
console.log(scores.toSpliced(1, 1, 99)); // [30, 99, 20]
console.log(scores.with(-1, 0)); // [30, 10, 0]
console.log(scores); // [30, 10, 20]
try {
scores.with(9, 0);
} catch (err) {
console.log(err.constructor.name); // RangeError
}go deeper
Know that ES2023 gave four array methods that return a new array instead of changing the original, and be able to name at least toSorted and toReversed alongside their mutating twins.
Explain the argument shapes and the return-value flip in toSpliced, mention that with throws a RangeError on an out-of-range index, and note that the copies are shallow.
Show you check the runtime baseline before using them, know they need a polyfill rather than transpilation, and can articulate when the copy cost is worth the safety on a hot path.
Own the rollout decision: whether the team adopts these across the codebase, how the lint rules or review conventions steer people away from in-place mutation, and what the polyfill budget buys.
## The gap they fill Before ES2023 the JavaScript array API was lopsided: `map`, `filter`, `slice` and `concat` returned new arrays, but the reordering and editing operations — `sort`, `reverse`, `splice`, and plain index assignment — all mutated in place. Writing non-mutating code meant copying first, which produced awkward two-step idioms: ```js const sorted = data.slice().sort(compare); const reversed = data.slice().reverse(); ``` ES2023 (the "Change Array by Copy" proposal) added four methods so the copying form is a single call. ## The four methods **`toSorted(compareFn?)`** — same argument as `sort`, returns a new sorted array. **`toReversed()`** — returns a new array in reverse order. **`toSpliced(start, skipCount, ...items)`** — returns a new array with `skipCount` elements removed at `start` and `items` inserted there. **`with(index, value)`** — returns a new array identical to the receiver except that `index` holds `value`. ```js const data = [3, 1, 2]; console.log(data.toSorted()); // [1, 2, 3] console.log(data.toReversed()); // [2, 1, 3] console.log(data.toSpliced(1, 1)); // [3, 2] console.log(data.with(0, 99)); // [99, 1, 2] console.log(data); // [3, 1, 2] — untouched every time ``` ## Differences that are easy to miss **The return value of `toSpliced` flips.** `splice` returns the elements it removed and keeps the result in the receiver; `toSpliced` returns the *result* and discards the removed elements. If you need both, you still want `splice` on a copy. **`with` validates its index.** `arr[10] = x` on a three-element array silently grows it and leaves holes; `arr.with(10, x)` throws a `RangeError`. Negative indexes count from the end, so `arr.with(-1, x)` replaces the last element. **They always produce dense arrays.** Sparse arrays — arrays with genuine holes such as `[1, , 3]` — are read element by element, so the result has `undefined` where the holes were, rather than preserving the holes the way `slice` does. **The copy is shallow.** `toSorted` gives you a new outer array whose elements are the same object references. Reordering is safe; mutating an element object is still visible through the original array. **They exist on typed arrays too**, with one exception: `TypedArray.prototype` has `toSorted`, `toReversed` and `with`, but no `toSpliced`, because typed arrays have a fixed length. ## Availability These landed in ES2023 and are available in Chrome 110+, Safari 16+, Firefox 115+ and Node 20+. In an environment older than that you fall back to copy-then-mutate: ```js // equivalent fallbacks const sorted = arr.slice().sort(compare); const reversed = arr.slice().reverse(); const replaced = arr.slice(); replaced[i] = value; ``` Because they are prototype methods with no syntax changes, they cannot be transpiled — a build tool can only supply them via a runtime polyfill, so check your minimum target before using them in shipped code. ## Why interviewers ask The question is a proxy for whether you understand the mutating/copying split at all. A candidate who knows the four new names almost always knows *why* they exist: accidental in-place mutation of shared arrays is one of the most common JavaScript bug classes, and the pre-2023 API made the safe path more verbose than the dangerous one. Being able to say "`sort` mutates, `toSorted` copies, and I reach for the copying one whenever the array is not exclusively mine" is the answer they want.
- How would you write the equivalent of arr.with(2, 'x') in a runtime that predates ES2023?Copy first, then assign: `const next = arr.slice(); next[2] = 'x';`. A one-liner alternative is `arr.map((v, i) => (i === 2 ? 'x' : v))`. Neither reproduces `with`'s RangeError on an out-of-range index — the manual assignment will happily grow the array and leave holes instead.
- Do these methods make the copies deep?No. They are shallow: the new array holds the same element references. Reordering or replacing whole elements is safe, but mutating a nested object — `copy[0].name = 'x'` — is still visible through the original array, because both arrays point at that same object.
- Why can't a transpiler add these to an older browser the way it handles arrow functions?They are library additions, not syntax. A transpiler rewrites syntax at build time, but `toSorted` is a method that must exist on `Array.prototype` at run time, so the only fix is a polyfill shipped with your bundle — which costs bytes and must be loaded before any code that calls it.
saying these in an interview costs you the question
- Claiming toSorted sorts the original array too
- Thinking toSpliced returns the removed elements
- Assuming these methods deep-copy nested objects
- Saying a transpiler can compile them for old browsers
- Believing with() grows the array when the index is out of range