ForgeFrontend — Prepare, Practice, Crack
Secure checkout
Lifetime access
Instant PDF download
Free updates forever

Prepare · Practice · Crack

slice vs splice in JavaScript

Short answer

slice copies a section of an array and returns the copy, leaving the original untouched. splice changes the original array in place — removing, replacing or inserting elements — and returns an array of whatever it removed. One letter apart, opposite behaviour.

slicesplice
Changes the originalNoYes
ReturnsA new array — the extracted partA new array — the removed part
Second argument meansEnd index (exclusive)How many to delete
Can insert elementsNoYes
Accepts negative indexesYes, both argumentsYes, for the start only
Works on stringsYesNo — arrays only
Safe for React stateYesNo
Called with no argumentsCopies the whole arrayDoes nothing
Two methods, one letter apart, almost nothing in common.

The difference in one example

Both take a starting index and both return an array, which is exactly why they get confused. The two things that actually differ are whether the original survives and what the returned array contains. slice hands you a copy of what you asked for. splice hands you what it just deleted, and the original array is now different.

Same call shape, opposite outcomejavascript
1const a = ["a", "b", "c", "d", "e"];
2a.slice(1, 3);
3// → ["b", "c"]        the extracted copy
4a;
5// → ["a","b","c","d","e"]   untouched
6
7const b = ["a", "b", "c", "d", "e"];
8b.splice(1, 3);
9// → ["b", "c", "d"]   what was REMOVED
10b;
11// → ["a", "e"]        the original, now shorter

Notice that the same numbers produced different results: `slice(1, 3)` gave two elements and `splice(1, 3)` gave three. That is not a quirk — the second argument means completely different things. For slice it is where to stop; for splice it is how many to remove.

slice: copying without touching

slice takes a start index and an optional end index, and the end is exclusive — the element at that index is not included. Both arguments are optional, and calling slice with no arguments at all gives you a shallow copy of the whole array, which is a common idiom on its own.

Every form of slicejavascript
1const items = ["a", "b", "c", "d", "e"];
2
3items.slice(2);        // → ["c", "d", "e"]   from index 2 to the end
4items.slice(1, 3);     // → ["b", "c"]        end is exclusive
5items.slice();         // → ["a","b","c","d","e"]  a full shallow copy
6items.slice(-2);       // → ["d", "e"]        last two
7items.slice(1, -1);    // → ["b", "c", "d"]   drop the first and last
8items.slice(3, 1);     // → []                end before start gives empty
9items.slice(10);       // → []                out of range, no error

Negative indexes count back from the end, and slice accepts them in both positions. `slice(-2)` is the idiomatic way to take the last N elements, and `slice(1, -1)` trims one from each end. Neither throws on out-of-range values; you just get an empty array, which makes slice safe to call on data you have not validated.

The patterns you will actually writejavascript
1// Paginate
2const page = rows.slice((n - 1) * 20, n * 20);   // → 20 rows for page n
3
4// Top N
5const top3 = [...scores].sort((a, b) => b - a).slice(0, 3);
6// → the spread matters: sort mutates, so we sort a copy
7
8// Everything except the first
9const [first, ...rest] = items;      // or items.slice(1)
10
11// Chunk an array
12function chunk(arr, size) {
13  return Array.from({ length: Math.ceil(arr.length / size) },
14    (_, i) => arr.slice(i * size, i * size + size));
15}
16chunk([1,2,3,4,5], 2);   // → [[1,2],[3,4],[5]]

splice: the multi-tool that changes everything

splice takes a start index, a delete count, and then any number of items to insert at that position. That signature makes it the only built-in that can remove and insert in one operation, which is why it survives despite being the least predictable array method in the language.

Remove, insert, replace — one methodjavascript
1let arr;
2
3arr = ["a","b","c","d"];
4arr.splice(1, 2);              // → ["b","c"] removed
5arr;                           // → ["a","d"]
6
7arr = ["a","b","c"];
8arr.splice(1, 0, "X", "Y");    // → []  nothing removed
9arr;                           // → ["a","X","Y","b","c"]   inserted
10
11arr = ["a","b","c"];
12arr.splice(1, 1, "B");         // → ["b"]
13arr;                           // → ["a","B","c"]           replaced
14
15arr = ["a","b","c"];
16arr.splice(1);                 // → ["b","c"]  no count = delete to the end
17arr;                           // → ["a"]

The other trap is the return value. Because splice returns the removed elements, assigning its result to the original variable is a specific and common way to destroy your data — and one that looks entirely reasonable at a glance.

The assignment that eats your arrayjavascript
1let items = ["a","b","c","d"];
2
3items = items.splice(1, 1);
4// → items is now ["b"] — the removed element, not the remaining array
5// The ["a","c","d"] you wanted was thrown away.
6
7// What you meant, one of:
8items.splice(1, 1);                 // → mutate in place, ignore the return
9const removed = items.splice(1, 1); // → keep both, explicitly
10const without = items.toSpliced(1, 1);  // → new array, original intact (ES2023)

This is 1 of 80+ questions in the JavaScript Interview Kit

Get every question with detailed answers, follow-ups and real code — plus coding challenges and a last-minute revision sheet. One-time payment, instant access.

⚡ Get the JavaScript Interview Kit → ₹299

Why this matters in React

This comparison looks like trivia until you meet it in a React component, where it stops being academic. React decides whether to re-render by comparing references. splice mutates the array in place, so the reference never changes, so React sees nothing to do — the data is updated and the screen is not.

The bug and the three fixesjsx
1const [items, setItems] = useState(["a","b","c"]);
2
3// Broken — same reference, no re-render
4const remove = (i) => {
5  items.splice(i, 1);
6  setItems(items);        // → screen never updates
7};
8
9// Fix 1 — filter, the usual choice
10const remove = (i) => setItems(items.filter((_, idx) => idx !== i));
11
12// Fix 2 — copy first, then splice the copy
13const remove = (i) => {
14  const next = [...items];
15  next.splice(i, 1);
16  setItems(next);         // → new reference, React re-renders
17};
18
19// Fix 3 — toSpliced, if your runtime has it
20const remove = (i) => setItems(items.toSpliced(i, 1));

The ES2023 copying methods

Four newer array methods return a copy instead of mutating, which removes the need to spread before every operation. They are supported in all current browsers and in Node 20 and later, though a project targeting older environments still needs the spread form.

MutatesCopiesReturns
splice()toSpliced()A new array with the change applied
sort()toSorted()A new sorted array
reverse()toReversed()A new reversed array
arr[i] = xwith(i, x)A new array with index i replaced
push / pop / shift[...arr, x] / sliceNo direct twin — use spread
Every mutating method now has a copying twin.
The difference in a React handlerjavascript
1// Before ES2023
2setItems(prev => {
3  const next = [...prev];
4  next.splice(index, 1);
5  return next;
6});
7
8// After
9setItems(prev => prev.toSpliced(index, 1));
10
11// And the one people reach for most:
12setItems(prev => prev.with(index, { ...prev[index], done: true }));
13// → replaces one item immutably, no spread gymnastics

slice on strings, and the substring family

String.prototype.slice exists and behaves exactly like the array version, negative indexes included. splice does not — strings are immutable, so there is nothing to mutate. That asymmetry is worth knowing because it makes slice the right default for both types.

slice, substring and substrjavascript
1const s = "ForgeFrontend";
2
3s.slice(5);          // → "Frontend"
4s.slice(0, 5);       // → "Forge"
5s.slice(-8);         // → "Frontend"    negatives work
6s.substring(-8);     // → "ForgeFrontend"  negatives become 0
7s.substring(5, 0);   // → "Forge"       arguments silently swapped
8s.substr(5, 3);      // → "Fro"         deprecated; length, not end index
9
10"abc".splice;
11// → undefined       strings have no splice at all

substring's habit of swapping its arguments when the second is smaller than the first makes it unpredictable with computed indexes, and substr is deprecated. slice is the one to use for strings and arrays alike, which is one fewer method to keep straight.

If you find yourself reaching for substring only because a negative index felt risky, that instinct is backwards. slice's handling of negatives is well defined and consistent across both types, and it is the reason a single mental model covers trimming a filename extension, taking the last few rows of a table and cutting a page out of a list.

Performance, honestly

slice allocates a new array, so it uses more memory; splice does not allocate but has to shift every element after the insertion point, which is O(n) for anything but the tail. Both are fast enough that the difference is irrelevant for the arrays a frontend actually holds.

The one case worth knowing is splice inside a loop. Removing items one at a time re-indexes the array on every call, which turns a linear job into a quadratic one — and it also skips elements, because the array shrinks under the loop counter. filter does it in one pass and does not have the skipping bug.

The loop bug that splice causesjavascript
1const nums = [1, 2, 2, 3, 2];
2
3for (let i = 0; i < nums.length; i++) {
4  if (nums[i] === 2) nums.splice(i, 1);   // shifts everything left
5}
6nums;
7// → [1, 2, 3]        one 2 survived: the loop skipped past it
8
9// Iterate backwards if you must splice in place:
10for (let i = nums.length - 1; i >= 0; i--) { … }
11
12// Or just don't:
13const cleaned = [1,2,2,3,2].filter(n => n !== 2);
14// → [1, 3]

What does splice return if nothing is removed?

An empty array. splice always returns an array of removed elements, so a pure insertion like splice(2, 0, "x") returns []. That is a genuinely useful signal: you can check the returned length to find out whether anything was actually removed.

Can splice take negative indexes?

The start index can be negative and counts back from the end, so splice(-1, 1) removes the last element. The delete count cannot be meaningfully negative — anything below zero is treated as zero, so nothing is removed and any insertion still happens.

What is the difference between slice and filter?

slice selects by position — a contiguous range of indexes. filter selects by predicate, keeping every element that passes a test, wherever it sits. Both return a new array and leave the original alone, so the choice is simply whether you are selecting by where an element is or by what it is.

Does slice work on array-like objects?

Yes, via borrowing: Array.prototype.slice.call(arguments) was the standard way to turn an arguments object or a NodeList into a real array before ES6. Array.from and the spread operator have replaced it in new code, but you will still meet the older form in library source.

Is slice a deep copy?

No, it is shallow. The new array holds the same references as the original, so mutating an object inside either array is visible in both. For a fully independent copy use structuredClone(arr), which handles nested objects, dates and maps.

Frequently asked questions

What is the difference between slice and splice in JavaScript?
slice returns a copy of a section of the array and leaves the original untouched. splice modifies the original array in place — removing, inserting or replacing elements — and returns an array containing whatever it removed. One copies, the other mutates.
Does slice modify the original array?
No, never. slice always returns a new array and the original is unchanged, which is why it is safe for React state and for any value other code might be holding. Note that the copy is shallow: objects inside it are still shared with the original.
What does splice return?
An array of the elements it removed, not the modified array. If nothing was removed — a pure insertion — it returns an empty array. Assigning that result back to your variable is a common bug that throws away everything you meant to keep.
How do I remove an element from an array without mutating it?
Use filter for a condition — items.filter(i => i.id !== id) — or toSpliced(index, 1) to remove by position. If you need to support older runtimes, copy first with the spread operator and splice the copy: const next = [...items]; next.splice(index, 1).
What is the second argument of slice and splice?
For slice it is the end index, and it is exclusive, so slice(1, 3) returns two elements. For splice it is the number of elements to delete, so splice(1, 3) removes three. Using one method's mental model for the other is the most common source of off-by-one errors here.
How do I insert an element into the middle of an array?
splice(index, 0, item) inserts without removing anything — the zero is the delete count. For an immutable version use toSpliced(index, 0, item), or build it with spread: [...items.slice(0, index), item, ...items.slice(index)].
Why doesn't my React component update after splice?
Because splice mutates the array in place, so the reference React compares against is identical and it concludes nothing changed. Produce a new array instead — filter, toSpliced, or a spread copy that you then splice — and pass that to the setter.
Do negative indexes work with slice and splice?
slice accepts negatives in both arguments, so slice(-2) takes the last two and slice(1, -1) drops the first and last. splice accepts a negative start index, so splice(-1, 1) removes the last element, but its delete count is clamped at zero.
Can you use splice on a string?
No. Strings are immutable in JavaScript, so there is nothing for splice to change and the method does not exist on String.prototype. String.prototype.slice does exist and behaves exactly like the array version, negative indexes included.
What is toSpliced?
The ES2023 copying version of splice: it takes the same arguments but returns a new array with the change applied and leaves the original alone. It ships alongside toSorted, toReversed and with, and it is supported in all current browsers and Node 20 and later.
Which is faster, slice or splice?
splice avoids allocating a new array but has to shift every element after the insertion point, so both are O(n) in the general case. The difference is irrelevant at realistic sizes. What does matter is calling splice inside a loop, which re-indexes on every call and also skips elements.
What is the difference between slice and substring?
Both extract part of a string, but slice handles negative indexes by counting from the end while substring converts them to zero. substring also silently swaps its arguments when the first is larger than the second. slice behaves predictably with computed indexes, which makes it the better default.

This is 1 of 80+ questions in the JavaScript Interview Kit

Get every question with detailed answers, follow-ups and real code — plus coding challenges and a last-minute revision sheet. One-time payment, instant access.

⚡ Get the JavaScript Interview Kit → ₹299
Written by Arun Karthikeyan · Last updated

Full kit

JavaScript Interview Kit · ₹299

Get it →