Array.prototype.with()

The non-mutating form of arr[i] = value. Unlike bracket assignment it refuses an out-of-range index instead of quietly creating a hole or a stray property.

Array methodES2023Live demo
Common call
items.with(i, value)
Returns
a new array with one element changed
Replaces
items.map((x, j) => j === i ? value : x)
Watch out
an out-of-range index is a RangeError, not a silent no-op
array.with(indexindex — Position to replace. Negative counts from the end. Anything outside the array throws RangeError.type: number · required, valuevalue — The replacement value for that position.type: any · required)
→ Array

Demo

Live evaluation
Try:
Inputs
itemsnumber[]numbers, comma separated
indexnumberindex to replace
valuenumbernew value
Output
[1, 2, 3].with(1, 9)
[1, 9, 3]

One element is replaced and everything else is copied through, leaving the source untouched. Negative indices count from the end, exactly like at(). The last case is what distinguishes this from bracket assignment: an index outside the array throws a RangeError rather than silently extending the array with a hole — which is what arr[99] = 9 would have done.

Parameters

NameTypeRequiredDescription
indexnumberyesPosition to replace. Negative counts from the end. Anything outside the array throws RangeError.
valueanyyesThe replacement value for that position.

Return value

Array — A NEW array identical to the original except at one index. Throws RangeError if the index is out of bounds.

Common patterns

Update one item in state
The cleanest non-mutating single-element update.
setItems(items.with(index, updated));
Replace the last element
A negative index avoids the length arithmetic.
const next = items.with(-1, replacement);
The pre-2023 equivalent
A map comparing indices, or a spread with splice.
const next = items.map((x, j) => j === i ? value : x);

Examples

1. Replace the middle
[1, 2, 3].with(1, 9)
Returns
[1, 9, 3]
2. Negative index
[1, 2, 3].with(-1, 9)
Returns
[1, 2, 9]
3. Original untouched
const a = [1, 2, 3]; a.with(0, 9); a
Returns
[1, 2, 3]
4. Out of range throws
[1, 2, 3].with(99, 0)
Returns
RangeError: Invalid index : 99
5. Brackets do not
const a = [1]; a[99] = 0; a.length
Returns
100
6. A different array
const a = [1]; a.with(0, 1) === a
Returns
false

Pitfalls

1. An out-of-range index throws
Unlike almost every other array operation, this one is strict. Bracket assignment at index 99 on a short array silently extends it with holes; with() refuses. That is a feature, but it is a behaviour change when converting existing code.
Throws
[1, 2, 3].with(99, 0)
RangeError: Invalid index : 99
Bounds-check first
if (i >= 0 && i < items.length) items.with(i, v);
safe
2. It replaces, it does not insert
The length never changes. To add an element at a position you want toSpliced with a skipCount of 0 — with() only overwrites what is already there.
Overwrites
[1, 2, 3].with(1, 9)
[1, 9, 3] // the 2 is gone
Insert instead
[1, 2, 3].toSpliced(1, 0, 9)
[1, 9, 2, 3]
3. ES2023 and newer only
Node 20+, and reasonably recent browsers. Note that `with` was also a legacy statement keyword, which is why the method reads oddly — it is only valid in method position.
Missing method
items.with(0, 1)
TypeError: items.with is not a function
Map instead
items.map((x, j) => j === 0 ? 1 : x)
same result

When to use

Use it
  • Replacing a single element in state or props
  • Updating by index inside an expression
  • Anywhere a map comparing indices is used just to swap one value
Reach for something else
  • Adding or removing elements → toSpliced
  • You own the array and want it changed in place → arr[i] = value
  • The index may be out of range → bounds-check, or it throws
  • Runtimes older than ES2023 → map with an index comparison

Notes

Complexity
O(n) — the whole array is copied
Return
A new array of the same length; the original is never modified
CPython impl
V8: Builtins-array-with.tq
Memory
Allocates a full copy
Thread-safe
Single-threaded; the source is only read

FAQ

Because bracket assignment on an array is really property assignment — arr[99] = 0 on a three-element array just sets a property and stretches the length to 100, leaving holes. with() is specified to reject an index outside the current bounds, which catches the mistake instead of producing a sparse array.

const a = [1];
a[99] = 0;
a.length;      // 100, with holes
a.with(99, 0);   // RangeError

History

ES2023
Added by the change-array-by-copy proposal, alongside toSorted, toReversed and toSpliced.