Skip to content
JavaScript js arrays 4 min read

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: splice returns the removed items, not the modified array. A common mistake is const result = arr.splice(0, 2) expecting result to 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 ]
Aspectslicesplice
Mutates originalNoYes
Return valueNew array of selected elementsArray of removed elements
Second argumentend index (exclusive)deleteCount
Can insert itemsNoYes (3rd+ arguments)
Negative indicesSupported for both argsSupported for start
Typical useCopy / read a rangeAdd, 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, while splice’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 toSpliced and toSorted on modern runtimes when you want splice-style edits without mutation.
Last updated June 1, 2026
Was this helpful?