splice()

ES3+

Changes the contents of an array by removing or replacing existing elements and/or adding new elements.

Syntax

array.splice(start, deleteCount, item1, item2, ...)

Parameters

start number

Index at which to start changing the array

deleteCount number optional

Number of elements to remove

Return Value

Array

An array containing the deleted elements

Examples

JavaScript
const fruits = ['apple', 'banana', 'cherry'];
fruits.splice(1, 1, 'blueberry', 'blackberry');
console.log(fruits);
Output:
// ['apple', 'blueberry', 'blackberry', 'cherry']

📌 When to Use

Use splice() when an array must change in place at an arbitrary position: delete the item at index i, insert new entries mid-list, or replace a run of elements - all three shapes come from the same signature depending on deleteCount and how many items you pass. It is the only built-in that performs positional insertion or removal on the existing array object, which matters when other code holds references to that same array and must observe the change, or when you are managing a large mutable buffer and want to avoid rebuilding it. Decide first whether mutation is actually acceptable. In React, Vue, Svelte, or Redux state, it usually is not - those systems detect changes by reference, so splice() edits the state invisibly; there, express the same intent immutably with filter() (remove), slice-and-spread or toSpliced() (insert/replace). For pure end operations, push/pop/shift/unshift are more specific and self-explanatory than splice() at position 0 or length. And when you find yourself splicing inside a loop over the same array, step back: batch the criteria into one filter() pass instead, which is simpler and avoids the index-shifting traps that make loop-splicing notorious.

⚠️ Common Mistakes

Confusing splice() with slice(). splice() is the destructive sibling: it edits the array you call it on and hands back the removed elements, while slice() copies and never touches the source. Using splice() where slice() was intended does not just return the wrong thing - it also quietly deletes data from an array other code may still depend on.

Chaining off the return value as if it were the updated array. splice() returns an array of the REMOVED elements (possibly empty), so const result = arr.splice(i, 1).map(...) maps over the deletion, not the survivors. The modified array is the original variable itself; use it on the next line rather than chaining.

Splicing framework state directly. React and Redux compare references to decide whether to re-render, and splice() keeps the same array reference while changing its contents - so the UI shows stale data or updates only when something unrelated triggers a render. Produce a new array instead: filter(), toSpliced(), or slice-and-spread.

Splicing inside a forward loop over the same array. Removing index i shifts every later element down, so the element that moves into slot i never gets examined when the loop increments. Symptoms are maddening: exactly every other matching element survives. Iterate backwards, or replace the whole loop with filter().

Calling splice() with an unchecked index from indexOf() or findIndex(). When the search misses, the -1 flows into splice(-1, 1), which deletes the LAST element - valid-looking behavior that corrupts data far from the actual bug. Guard the index before every splice.

Omitting deleteCount while intending an insert. arr.splice(2) with no second argument truncates everything from index 2 to the end. Insertion requires the explicit zero: arr.splice(2, 0, item).

✅ Best Practices

Prefer toSpliced() (ES2023) or slice-and-spread when the surrounding code expects immutability - same positional semantics, new array out. Reserve true splice() for genuinely mutable structures you own outright, like an internal queue or an undo buffer.

Memorize the three canonical shapes and write them idiomatically: remove with splice(i, n), insert with splice(i, 0, ...items), replace with splice(i, n, ...items). The middle argument being zero is what makes an operation a pure insertion - commenting the intent helps readers who never remember the signature.

Negative start indices count from the end - arr.splice(-2, 1) removes the second-to-last element - which saves length arithmetic, but for the plain "remove last" case pop() is clearer and returns the element directly instead of wrapped in an array.

Capture removed elements when they matter: const [removed] = arr.splice(i, 1) destructures the single deletion cleanly, which is exactly what undo features and drag-and-drop reordering need - remove from one position, hold the item, splice it in elsewhere.

Empty an array in place with arr.splice(0) (or arr.length = 0) when other modules hold references to it and must see it emptied; reassigning arr = [] only rebinds your local variable and leaves every other reference pointing at the old, still-populated array.

⚡ Performance Notes

splice() is O(n - i): every element after the splice point shifts to close or open the gap, so edits near the front of a large array are the expensive ones while edits near the tail cost almost nothing. It also allocates a small array for the removed elements even when you ignore it. The pattern that actually hurts in practice is not one splice but many - removing m matching items from an n-element array via repeated splice() is O(n*m), while a single filter() pass is O(n) with one allocation, so batch removals should nearly always be filters. Beware, too, that heavy mid-array mutation can push V8 arrays into less optimal element kinds; a workload that constantly inserts and deletes in the middle of a 100k+ element collection is better served by a different structure - a linked list, a skip list, or often simply a Map keyed by id when order can be derived elsewhere. For UI-scale arrays (hundreds of items), none of this is measurable and splice() is fine.

🌍 Real World Example

Managing a Todo List

A mutable todo list exercises all three splice() shapes in a few lines: inserting a new task at a chosen position (deleteCount 0), removing one by index while capturing it through destructuring - the captured value is what an undo stack or a "task deleted" toast with restore needs - and replacing an entry outright by deleting one and inserting another at the same index. This mutating style suits a plain-JavaScript model layer or a CLI tool that owns its data outright. The same operations in framework state would each become a toSpliced() call or a spread construction, with identical index arithmetic but a fresh array returned every time.

const todos = [
  {id: 1, text: 'Learn JavaScript'},
  {id: 2, text: 'Build a project'},
  {id: 3, text: 'Deploy to production'}
];

// Insert at position 1
todos.splice(1, 0, {id: 4, text: 'Practice arrays'});

// Remove item at position 2
const [removed] = todos.splice(2, 1);

// Replace item at position 0
todos.splice(0, 1, {id: 5, text: 'Master JavaScript'});

Related Methods