slice vs splice
Two array methods with nearly identical names do almost opposite things. slice reads a range of elements and hands back a brand-new array, leaving the original untouched. splice reaches into the original array and rewrites it in place, removing or inserting elements and returning whatever it pulled out. Mixing them up is one of the most common sources of accidental mutation bugs, so it pays to know exactly how each behaves.
How slice works
slice(start, end) returns a shallow copy of a portion of an array. The start index is inclusive and end is exclusive, so the result never includes the element at end. Crucially, the source array is never modified.
const colors = ["red", "green", "blue", "yellow"];
const middle = colors.slice(1, 3);
console.log(middle);
console.log(colors);
Output:
[ 'green', 'blue' ]
[ 'red', 'green', 'blue', 'yellow' ]
Both arguments are optional. Omitting end slices through to the last element, and omitting both copies the whole array — a handy idiom for cloning. Negative indices count back from the end.
const nums = [10, 20, 30, 40, 50];
console.log(nums.slice(2)); // from index 2 onward
console.log(nums.slice(-2)); // last two elements
console.log([...nums].length); // spread is the modern clone, slice still works
Output:
[ 30, 40, 50 ]
[ 40, 50 ]
5
Tip:
arr.slice()produces a shallow copy. Nested objects and arrays are shared by reference, so mutating a nested value still affects both arrays.
How splice works
splice(start, deleteCount, ...items) mutates the array. It removes deleteCount elements beginning at start, optionally inserts the provided items at that position, and returns an array of the removed elements.
const queue = ["a", "b", "c", "d"];
const removed = queue.splice(1, 2);
console.log(removed);
console.log(queue);
Output:
[ 'b', 'c' ]
[ 'a', 'd' ]
Because you control all three behaviours through one call, splice can delete, insert, and replace in a single operation.
const tasks = ["wake", "code", "sleep"];
// Insert without deleting: deleteCount = 0
tasks.splice(2, 0, "eat", "exercise");
console.log(tasks);
// Replace one element with another
tasks.splice(0, 1, "stretch");
console.log(tasks);
Output:
[ 'wake', 'code', 'eat', 'exercise', 'sleep' ]
[ 'stretch', 'code', 'eat', 'exercise', 'sleep' ]
Warning:
splicereturns the removed items, not the modified array. A common mistake isconst result = arr.splice(0, 2)expectingresultto be the leftover array — it is the deleted slice instead.
Side-by-side comparison
const original = [1, 2, 3, 4, 5];
const copy = original.slice(1, 4); // non-mutating
console.log("after slice:", original); // unchanged
const pulled = original.splice(1, 2); // mutating
console.log("after splice:", original); // changed
console.log("pulled out:", pulled);
Output:
after slice: [ 1, 2, 3, 4, 5 ]
after splice: [ 1, 4, 5 ]
pulled out: [ 2, 3 ]
| Aspect | slice | splice |
|---|---|---|
| Mutates original | No | Yes |
| Return value | New array of selected elements | Array of removed elements |
| Second argument | end index (exclusive) | deleteCount |
| Can insert items | No | Yes (3rd+ arguments) |
| Negative indices | Supported for both args | Supported for start |
| Typical use | Copy / read a range | Add, remove, or replace in place |
Choosing between them
Reach for slice whenever you need a snapshot, a clone, or a sub-range without disturbing the source — this is essential in React state updates and other immutable-data workflows. Reach for splice when in-place editing is exactly what you want, such as removing an item from a working buffer by index.
If you like splice’s power but want immutability, ES2023 added toSpliced, a non-mutating counterpart that returns a new array:
const arr = [1, 2, 3, 4];
const updated = arr.toSpliced(1, 2, 99);
console.log(updated);
console.log(arr);
Output:
[ 1, 99, 4 ]
[ 1, 2, 3, 4 ]
Best Practices
- Use
slice()(or the spread operator) to clone an array before modifying it when the original must stay intact. - Remember
slice’s second argument is an exclusive end index, whilesplice’s second argument is a count — they are not interchangeable. - Capture
splice’s return value only when you actually need the removed elements; otherwise call it for its side effect. - Prefer non-mutating operations (
slice,toSpliced, spread) in state management code to avoid hard-to-trace bugs. - Treat both copies as shallow: deep-clone with
structuredClone()when nested data must be independent. - Use
toSplicedandtoSortedon modern runtimes when you wantsplice-style edits without mutation.