JavaScript splice Returns an Empty Array: Get the Edited Result
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 result | Operation | Value to keep |
|---|---|---|
| Edit the existing array | array.splice(index, 0, item) | array, after the call |
| Collect removed elements | array.splice(index, count) | The call’s return value |
| Create an edited array | array.toSpliced(index, count, item) | The call’s return value |
| Edit a copy using splice | Copy 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 matchshallow copy: retained object reference is sharedThe 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, whilesplice(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: