Immutable Array Updates with Array.prototype.with
The Array.prototype.with() method, introduced in
ECMAScript 2023 (ES14), allows developers to update a specific element
in an array by index without modifying the original array. Instead of
mutating the source in place, it creates and returns a shallow copy of
the array with the specified index updated to the new value. This
article explains how the method works, its syntax, key characteristics,
and how it simplifies immutable state management in modern
JavaScript.
Syntax and Basic Usage
The syntax for Array.prototype.with() is
straightforward:
array.with(index, value)index: The zero-based index of the element to modify. It also accepts negative integers, which count backward from the end of the array (e.g.,-1refers to the last element).value: The new value assigned to the specified index.
Example: Mutable vs. Immutable Updates
Traditionally, updating an array element directly with bracket notation mutates the original array:
const original = ['apple', 'banana', 'cherry'];
// Mutable approach (modifies 'original')
original[1] = 'blueberry';
console.log(original); // ['apple', 'blueberry', 'cherry']With Array.prototype.with(), the original array remains
untouched, and a new array is returned:
const fruits = ['apple', 'banana', 'cherry'];
const updatedFruits = fruits.with(1, 'blueberry');
console.log(fruits); // ['apple', 'banana', 'cherry'] (unchanged)
console.log(updatedFruits); // ['apple', 'blueberry', 'cherry']Key Characteristics
of Array.prototype.with()
1. Negative Indexing
Unlike bracket notation (arr[arr.length - 1]),
.with() natively supports negative indices:
const numbers = [10, 20, 30, 40];
const modified = numbers.with(-1, 99);
console.log(modified); // [10, 20, 30, 99]2. Error Handling on Out-of-Bounds Indices
If the specified index is greater than or equal to
array.length, or if negative indexing extends beyond the
start of the array, Array.prototype.with() throws a
RangeError:
const items = ['a', 'b'];
items.with(5, 'c'); // Throws RangeError: Invalid index : 5
items.with(-3, 'c'); // Throws RangeError: Invalid index : -33. Handling Sparse Arrays
When used on a sparse array (an array containing empty slots),
.with() creates a dense array where any empty slots become
undefined:
const sparse = [1, , 3]; // Contains an empty slot at index 1
const dense = sparse.with(0, 10);
console.log(dense); // [10, undefined, 3]Why Use
Array.prototype.with()?
Before this method, achieving immutability required spreading the array or using slicing techniques:
// Pre-ES2023 workaround
const updated = [
...original.slice(0, targetIndex),
newValue,
...original.slice(targetIndex + 1)
];Array.prototype.with() eliminates boilerplate code,
reduces cognitive overhead, and improves readability. It is particularly
useful in environments relying on immutable state patterns, such as
React, Redux, or functional programming pipelines.