864 words
4 minutes

JavaScript splice Returns an Empty Array: Get the Edited Result

2026-10-26
Frontend
JavaScript
/
Node.js
/
Troubleshooting
Contents

array.splice(1, 0, 'X') returns [] because it removes zero elements. The insertion still changes array. If you need the array after insertion, call splice() first and then read or return array. If the original must stay unchanged, use array.toSpliced(1, 0, 'X') and keep its return value.

An empty return value alone cannot tell you whether the operation changed anything. The fixture below checks both the removed elements and the array after each call, then verifies two ways to create an edited copy.

Separate the removed elements from the edited array#

MDN’s splice reference defines the return value as the deleted elements. That makes return array.splice(index, 0, item) the wrong return expression for an insertion helper that promises the completed array.

For a helper intended to mutate its input, use two statements: array.splice(index, 0, item); return array;. All existing references to that array observe the edit. Changing the return statement does not undo the mutation.

For a helper intended to leave its input intact, return array.toSpliced(index, 0, item). The toSpliced reference describes its return as the new edited array. Check that the target runtime supports this method. The fixture also checks a copy-first alternative: const copy = array.slice();, splice the copy, then return the copy.

Required resultOperationValue to keep
Edit the existing arrayarray.splice(index, 0, item)array, after the call
Collect removed elementsarray.splice(index, count)The call’s return value
Create an edited arrayarray.toSpliced(index, count, item)The call’s return value
Edit a copy using spliceCopy with slice(), then call copy.splice(...)copy, after the call

Verify both sides of each call#

Save the following as splice-check.mjs and run node splice-check.mjs. These assertions were executed during article preparation on October 26, 2026, with Node.js 24.13.0 on macOS. They use ordinary dense arrays and require no packages.

import assert from 'node:assert/strict';
const rows = [
['insert', [1, 0, 'X'], [], ['A', 'X', 'B', 'C']],
['replace', [1, 1, 'X'], ['B'], ['A', 'X', 'C']],
['omitted count', [1], ['B', 'C'], ['A']],
['undefined count', [1, undefined, 'X'], [], ['A', 'X', 'B', 'C']],
['replace tail', [1, Infinity, 'X'], ['B', 'C'], ['A', 'X']],
['past end', [99, 1], [], ['A', 'B', 'C']],
['no arguments', [], [], ['A', 'B', 'C']],
['undefined start', [undefined], ['A', 'B', 'C'], []],
];
for (const [label, args, expectedRemoved, expectedAfter] of rows) {
const array = ['A', 'B', 'C'];
const alias = array;
const removed = array.splice(...args);
assert.deepEqual(removed, expectedRemoved);
assert.deepEqual(array, expectedAfter);
assert.equal(alias, array);
assert.notEqual(removed, array);
console.log(`${label}: removed=${JSON.stringify(removed)} after=${JSON.stringify(array)}`);
}
const original = ['A', 'B', 'C'];
const next = original.toSpliced(1, 0, 'X');
assert.deepEqual(original, ['A', 'B', 'C']);
assert.deepEqual(next, ['A', 'X', 'B', 'C']);
assert.notEqual(next, original);
const copy = original.slice();
assert.deepEqual(copy.splice(1, 0, 'X'), []);
assert.deepEqual(copy, next);
assert.deepEqual(original, ['A', 'B', 'C']);
console.log('copy paths: original unchanged; edited arrays match');
const record = { done: false };
const records = [record];
const edited = records.toSpliced(1, 0, { done: true });
assert.equal(edited[0], records[0]);
edited[0].done = true;
assert.equal(records[0].done, true);
console.log('shallow copy: retained object reference is shared');

Expected output:

insert: removed=[] after=["A","X","B","C"]
replace: removed=["B"] after=["A","X","C"]
omitted count: removed=["B","C"] after=["A"]
undefined count: removed=[] after=["A","X","B","C"]
replace tail: removed=["B","C"] after=["A","X"]
past end: removed=[] after=["A","B","C"]
no arguments: removed=[] after=["A","B","C"]
undefined start: removed=["A","B","C"] after=[]
copy paths: original unchanged; edited arrays match
shallow copy: retained object reference is shared

The insertion and past-end rows both return [], but only the insertion row changes the elements. The replacement row returns ['B'], while the edited array contains 'X' at that position. Each iteration asserts that the returned array is a different object from the array being edited.

The alias assertion verifies that splice() keeps the original array identity. It does not produce a replacement array for references elsewhere in the program. If a caller needs to retain the old contents, copy before mutating.

Preserve argument presence when wrapping splice#

The ECMAScript splice algorithm distinguishes a missing argument from an argument whose value is undefined. The fixture makes that boundary visible:

  • splice(1) removes the remaining tail.
  • splice(1, undefined, 'X') inserts without removing the tail.
  • splice(1, Infinity, 'X') replaces the remaining tail with 'X'.
  • splice() removes nothing, while splice(undefined) removes everything in this input.

This matters in a wrapper that always forwards a second argument. Calling a wrapper with only start does not preserve native one-argument behavior if the wrapper calls array.splice(start, count) with an undefined count. Either require an explicit count in the wrapper’s API or forward the original argument list when native omission semantics are intended. The fixture uses argument arrays and spread specifically to preserve that difference.

For insertion-only code, pass 0 explicitly. It communicates the intended removal count and avoids relying on a missing parameter to mean something it does not.

A new array can still share its objects#

The final assertion changes edited[0].done and observes the change through records[0]. The outer arrays differ, but the retained record is the same object. Choosing toSpliced() solves the outer-array mutation problem; it does not promise independent nested data. The separate nested object copying guide explains why copying a container does not recursively copy its contents.

These examples do not test browser engines, framework state updates, sparse arrays, subclasses, getters, or proxies. In particular, the copy-first path is checked for dense arrays here; it is not presented as a complete polyfill for toSpliced().

Choose what the caller should receive before changing the code: removed elements, the same edited array, or a new edited array. Then assert both the returned value and the original contents so an empty removal list cannot hide a successful insertion.

References:

JavaScript splice Returns an Empty Array: Get the Edited Result
https://laplusda.com/en/posts/javascript-splice-returns-empty-array/
Author
Zero
Published at
2026-10-26
License
CC BY-NC-SA 4.0