In JavaScript, slice(start, end) returns a copy of part of an array or string and leaves the original alone. splice(start, deleteCount, ...items) changes the array itself: it removes, inserts or replaces items and returns the removed ones. split(separator) is a string method that breaks text into an array. If you need splice-style changes without mutating, use toSpliced().
The names differ by one or two letters, which is why they get mixed up so often. The second argument also means something different in slice (an end position) and splice (a count), and that one detail causes most of the bugs.
All output below comes from Node.js v24.22.1. The behaviour is the same in modern browsers, since these methods are part of the language itself.
In this guide:
- slice(): copy part of an array without changing it
- splice(): remove, insert and replace in place
- split(): turn a string into an array
- Strings have slice() but not splice()
- Mistake 1: treating the second argument the same way
- Mistake 2: splice() inside a forward loop
- Mistake 3: splice() on React state
- toSpliced(): splice without changing the original
- slice() makes a shallow copy
- split() with join() and destructuring
- Is slice() slow?
- slice vs splice vs split compared
- Frequently asked questions
slice(): copy part of an array without changing it
const fruits = ["apple", "banana", "cherry", "date", "elder"];
console.log(fruits.slice(1, 3));
console.log(fruits.slice(2));
console.log(fruits.slice(-2));
console.log(fruits.slice());
console.log("original:", fruits);
[ 'banana', 'cherry' ]
[ 'cherry', 'date', 'elder' ]
[ 'date', 'elder' ]
[ 'apple', 'banana', 'cherry', 'date', 'elder' ]
original: [ 'apple', 'banana', 'cherry', 'date', 'elder' ]
slice(1, 3) gives the items at index 1 and 2: the end index is not included. Leave out the end to go to the last item, use a negative start to count from the end, and call slice() with no arguments for a full copy. The last line shows the original array was never touched. The MDN reference for Array.prototype.slice() describes it as returning a shallow copy.
splice(): remove, insert and replace in place
const fruits = ["apple", "banana", "cherry", "date", "elder"];
const removed = fruits.splice(1, 2);
console.log("removed:", removed);
console.log("after remove:", fruits);
fruits.splice(1, 0, "blueberry", "coconut");
console.log("after insert:", fruits);
const replaced = fruits.splice(-1, 1, "fig");
console.log("replaced:", replaced, "->", fruits);
removed: [ 'banana', 'cherry' ]
after remove: [ 'apple', 'date', 'elder' ]
after insert: [ 'apple', 'blueberry', 'coconut', 'date', 'elder' ]
replaced: [ 'elder' ] -> [ 'apple', 'blueberry', 'coconut', 'date', 'fig' ]
One method does three jobs. splice(1, 2) removed two items starting at index 1 and returned them. splice(1, 0, ...) removed nothing and inserted two new items. splice(-1, 1, "fig") replaced the last item. Every call changed fruits itself, which is the key difference from slice. The MDN reference for splice() lists all the argument rules.
split(): turn a string into an array
const csv = "Ana,31,London";
console.log(csv.split(","));
console.log("a b c".split(" "));
console.log("a b c".split(/\s+/));
console.log("hello".split(""));
console.log("one,two,three,four".split(",", 2));
console.log("no-commas".split(","));
console.log("".split(","));
[ 'Ana', '31', 'London' ]
[ 'a', 'b', '', 'c' ]
[ 'a', 'b', 'c' ]
[ 'h', 'e', 'l', 'l', 'o' ]
[ 'one', 'two' ]
[ 'no-commas' ]
[ '' ]
A few results are worth remembering. Two spaces in a row produced an empty string, which a regular expression like /\s+/ avoids. The second argument is a limit, not a position. A string without the separator gives an array with one item, and an empty string gives [''], not an empty array, so check for that before you count fields. The MDN reference for split() covers regular expression separators in detail.
Strings have slice() but not splice()
const s = "JavaScript";
console.log(s.slice(0, 4), s.slice(-6), s.slice(4, 1) === "");
console.log(s.substring(4, 1));
try {
s.splice(0, 4);
} catch (e) {
console.log(`${e.name}: ${e.message}`);
}
Java Script true
ava
TypeError: s.splice is not a function
slice() works on strings the same way it does on arrays. Strings cannot be changed in place, so there is no splice() and calling it throws TypeError: s.splice is not a function, a cousin of the errors in my guide to TypeError: x is not a function. Note substring(4, 1): unlike slice, it swaps the arguments when start is bigger than end, which is why it returned ava while slice(4, 1) returned an empty string.
Mistake 1: treating the second argument the same way
const a = [10, 20, 30, 40, 50];
const b = [10, 20, 30, 40, 50];
console.log("slice(1, 3):", a.slice(1, 3));
console.log("splice(1, 3):", b.splice(1, 3), "left:", b);
slice(1, 3): [ 20, 30 ]
splice(1, 3): [ 20, 30, 40 ] left: [ 10, 50 ]
Same numbers, different results. For slice(1, 3), 3 is the end position, so you get two items. For splice(1, 3), 3 is how many items to delete, so you get three items and the array shrinks to [10, 50]. When a splice deletes more than you expected, this is almost always the reason.
Mistake 2: splice() inside a forward loop
Removing items while looping forward skips elements, because everything after the removed item moves one place left:
const nums = [1, 2, 2, 3, 2, 4];
for (let i = 0; i < nums.length; i++) {
if (nums[i] === 2) nums.splice(i, 1);
}
console.log("buggy:", nums);
const nums2 = [1, 2, 2, 3, 2, 4];
for (let i = nums2.length - 1; i >= 0; i--) {
if (nums2[i] === 2) nums2.splice(i, 1);
}
console.log("backwards:", nums2);
console.log("filter:", [1, 2, 2, 3, 2, 4].filter((n) => n !== 2));
buggy: [ 1, 2, 3, 4 ]
backwards: [ 1, 3, 4 ]
filter: [ 1, 3, 4 ]
The buggy loop left a 2 behind: after removing the first 2, the second one moved into the current index and the loop stepped past it. Looping backwards avoids the shift, and filter() avoids the problem entirely by building a new array. My guide to map, filter and reduce covers filter() in more depth.
Mistake 3: splice() on React state
React decides whether to re-render by checking whether the state value is a different object, using Object.is. This script runs the same check without React:
// Simulates React's Object.is check: a mutated array is the "same" state
let state = ["a", "b", "c"];
const prev = state;
state.splice(1, 1);
console.log("splice: changed?", !Object.is(prev, state), state);
let state2 = ["a", "b", "c"];
const prev2 = state2;
state2 = [...state2.slice(0, 1), ...state2.slice(2)];
console.log("slice: changed?", !Object.is(prev2, state2), state2);
const state3 = ["a", "b", "c"];
const next3 = state3.toSpliced(1, 1);
console.log("toSpliced: changed?", !Object.is(state3, next3), next3, state3);
splice: changed? false [ 'a', 'c' ]
slice: changed? true [ 'a', 'c' ]
toSpliced: changed? true [ 'a', 'c' ] [ 'a', 'b', 'c' ]
After splice(), the array is the same object, so React would see no change and skip the update. Building a new array with slice() and spread, or with toSpliced(), produces a new object. The React guide to updating arrays in state lists splice among the methods to avoid and recommends the non-mutating ones.
toSpliced(): splice without changing the original
toSpliced() takes the same arguments as splice(), but returns a new array and leaves the original alone, as the last line of the React example shows: next3 lost b, while state3 still has it. It arrived with ES2023, together with toSorted() and toReversed(), and the MDN toSpliced() page has the browser support table. Note one difference: it returns the new array, not the removed items.
slice() makes a shallow copy
const users = [{ name: "Ana" }, { name: "Ben" }];
const copy = users.slice();
copy[0].name = "Changed";
copy.push({ name: "Cy" });
console.log(users);
console.log(copy.length, users.length);
[ { name: 'Changed' }, { name: 'Ben' } ]
3 2
Pushing to the copy did not affect the original, but changing an object inside the copy changed it in both, because both arrays hold the same objects. If you need fully independent nested data, use structuredClone(), which my guide to deep copying objects in JavaScript compares with the other options.
split() with join() and destructuring
const slug = " Hello World From JS ".trim().toLowerCase().split(" ").join("-");
console.log(slug);
const [first, ...rest] = "2026-11-09".split("-");
console.log(first, rest);
console.log([..."héllo👋"].length, "héllo👋".split("").length);
hello-world-from-js
2026 [ '11', '09' ]
6 7
split and join are a natural pair for quick text transforms like building a slug, and destructuring with ...rest picks apart a date string neatly. The last line shows a trap: split("") splits by UTF-16 code unit, so the emoji became two broken pieces and the count was 7. Spreading the string with [...text] splits by character and gave 6. For proper URL slugs, my post on what a URL slug is covers the rules.
Is slice() slow?
Copying costs time in proportion to how much you copy. I timed each call 200,000 times, best of 5 runs:
const N = 200_000;
const base = Array.from({ length: 1000 }, (_, i) => i);
function bench(label, fn) {
let best = Infinity;
for (let r = 0; r < 5; r++) {
const t = performance.now();
for (let i = 0; i < N; i++) fn();
best = Math.min(best, performance.now() - t);
}
console.log(`${label.padEnd(34)} ${((best / N) * 1e6).toFixed(0)} ns per call`);
}
bench("slice(0, 10) of 1000", () => base.slice(0, 10));
bench("slice() full copy of 1000", () => base.slice());
bench("copy + splice(500, 1) of 1000", () => base.slice().splice(500, 1));
bench("toSpliced(500, 1) of 1000", () => base.toSpliced(500, 1));
slice(0, 10) of 1000 18 ns per call
slice() full copy of 1000 584 ns per call
copy + splice(500, 1) of 1000 637 ns per call
toSpliced(500, 1) of 1000 593 ns per call
Taking 10 items was far cheaper than copying all 1,000, and copying then splicing cost about the same as toSpliced(). These numbers come from one machine and vary between runs, so take only the pattern from them: the size of the copy matters, the method name does not. For typical UI lists with hundreds of items, any of these is fast enough. More small browser-based helpers are on my free tools page.
slice vs splice vs split compared
| slice() | splice() | split() |
|---|
| Works on | Arrays and strings | Arrays only | Strings only |
| Changes the original? | No | Yes | No (strings are immutable) |
| Arguments | (start, end), end not included | (start, deleteCount, ...items) | (separator, limit) |
| Returns | New array or string with the selected part | Array of the removed items | New array of string pieces |
| Negative index | Counts from the end | Counts from the end | Not applicable |
| Non-mutating alternative | Already non-mutating | toSpliced() | Already non-mutating |
| Typical use | Copy or take part of a list or string | Remove, insert or replace items in place | Turn text into a list |
A quick way to remember: splice has a "p" for "in place", slice takes a slice and leaves the rest, and split splits text. The MDN String.prototype.slice() page covers the string version's index rules.
Frequently asked questions
What is the difference between slice and splice in JavaScript?
slice(start, end) returns a copy of part of an array without changing it. splice(start, deleteCount, ...items) changes the original array by removing, inserting or replacing items, and returns the removed items.
Does slice modify the original array?
No. slice always returns a new array or string and leaves the original unchanged. It is a shallow copy, so objects inside are still shared.
What does splice return?
An array of the items it removed. If it removed nothing, for example when only inserting, it returns an empty array.
Can I use splice on a string?
No. Strings are immutable and have no splice method. Use slice, or split the string into an array, change it, and join it back.
What is toSpliced in JavaScript?
toSpliced is the non-mutating version of splice, added in ES2023. It takes the same arguments and returns a new array, leaving the original unchanged.
Why does "".split(",") return an array with one item?
split always returns at least one piece. An empty string with no separator gives [""]. Check for an empty string first if you need an empty array.
Should I use splice in React state updates?
No. splice mutates the array, so React sees the same object and may not re-render. Use filter, slice with spread, or toSpliced to create a new array.
Last checked: 9 November 2026. Figures, settings and guidance can change, so confirm anything important with the sources linked above.