JavaScript Array Slice Method: A Complete Guide
Master the JavaScript slice() method for extracting portions of arrays without mutation. Covers syntax, negative indices, shallow copying, real-world extraction patterns, and how slice differs from splice.
The slice() method is one of the most important non-mutating array methods in JavaScript. It extracts a section of an array and returns it as a new array, leaving the original untouched. This makes slice() essential for immutable data patterns in React, Redux, and any functional programming approach. If you have been using push(), pop(), shift(), and unshift() to manipulate arrays, slice() is the tool you reach for when you need extraction without side effects.
Syntax
array.slice()
array.slice(start)
array.slice(start, end)| Parameter | Type | Description |
|---|---|---|
start | Number (optional) | Index where extraction begins (inclusive). Defaults to 0 |
end | Number (optional) | Index where extraction stops (exclusive). Defaults to array.length |
Key rule: start is inclusive, end is exclusive. The element at the end index is not included in the result.
Basic Usage
const months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun"];
// Extract from index 1 to 4 (not including 4)
const spring = months.slice(1, 4);
console.log(spring); // ["Feb", "Mar", "Apr"]
// Original is unchanged
console.log(months); // ["Jan", "Feb", "Mar", "Apr", "May", "Jun"]One Argument: Slice From Index to End
const numbers = [10, 20, 30, 40, 50];
const fromTwo = numbers.slice(2);
console.log(fromTwo); // [30, 40, 50]
const fromFour = numbers.slice(4);
console.log(fromFour); // [50]No Arguments: Shallow Clone
Calling slice() with no arguments returns a shallow copy of the entire array:
const original = [1, 2, 3, 4, 5];
const clone = original.slice();
clone.push(6);
console.log(original); // [1, 2, 3, 4, 5] — unchanged
console.log(clone); // [1, 2, 3, 4, 5, 6]This is equivalent to [...original] or Array.from(original), but slice() was available long before ES6.
Negative Indices
Negative values count backward from the end of the array. -1 refers to the last element, -2 to the second-to-last, and so on:
const letters = ["a", "b", "c", "d", "e"];
// Last 2 elements
console.log(letters.slice(-2)); // ["d", "e"]
// From third-to-last to second-to-last
console.log(letters.slice(-3, -1)); // ["c", "d"]
// Everything except the last element
console.log(letters.slice(0, -1)); // ["a", "b", "c", "d"]| Expression | Start (resolved) | End (resolved) | Result |
|---|---|---|---|
slice(-2) | 3 | 5 | ["d", "e"] |
slice(-3, -1) | 2 | 4 | ["c", "d"] |
slice(0, -1) | 0 | 4 | ["a", "b", "c", "d"] |
slice(1, -2) | 1 | 3 | ["b", "c"] |
Edge Cases and Boundary Behavior
Understanding how slice() handles unusual inputs prevents surprises:
const arr = [10, 20, 30, 40, 50];
// Start beyond length: empty array
console.log(arr.slice(10)); // []
// End beyond length: same as slicing to the end
console.log(arr.slice(2, 100)); // [30, 40, 50]
// Start >= end: empty array
console.log(arr.slice(3, 1)); // []
// Both negative, start < end
console.log(arr.slice(-4, -2)); // [20, 30]
// Undefined parameters: treated as defaults
console.log(arr.slice(undefined, 3)); // [10, 20, 30]| Scenario | Input | Result | Why |
|---|---|---|---|
| Start past end | slice(10) | [] | No elements from index 10 onward |
| End past length | slice(2, 100) | [30, 40, 50] | Clamped to array length |
| Start >= end | slice(3, 1) | [] | Zero-width range |
| Both undefined | slice() | Full clone | Defaults to (0, length) |
Shallow Copy Behavior
slice() creates a shallow copy, meaning it copies element references, not the elements themselves. This matters when your array contains objects:
const users = [
{ name: "Alice", score: 95 },
{ name: "Bob", score: 82 },
{ name: "Carol", score: 91 },
];
const topTwo = users.slice(0, 2);
// Modifying the object inside the copy affects the original
topTwo[0].score = 100;
console.log(users[0].score); // 100 — original object was mutated!
// But adding to the copy does not affect the original array
topTwo.push({ name: "Dave", score: 77 });
console.log(users.length); // 3 — unchanged
console.log(topTwo.length); // 3Shallow Copy Trap
Slicing an array of objects gives you a new array, but the objects inside are still shared references. If you need truly independent copies, use structuredClone() or map with object spread: arr.map(obj => ({ ...obj })).
Real-World Patterns
Pagination
Extracting a page of results from a dataset:
function paginate(items, page, pageSize) {
const start = (page - 1) * pageSize;
const end = start + pageSize;
return {
data: items.slice(start, end),
page,
totalPages: Math.ceil(items.length / pageSize),
totalItems: items.length,
};
}
const products = Array.from({ length: 47 }, (_, i) => ({
id: i + 1,
name: `Product ${i + 1}`,
}));
const page2 = paginate(products, 2, 10);
console.log(page2.data.length); // 10
console.log(page2.data[0].name); // "Product 11"
console.log(page2.totalPages); // 5Removing the First or Last N Elements (Immutably)
const items = ["a", "b", "c", "d", "e"];
// Remove first element (immutable shift)
const withoutFirst = items.slice(1);
console.log(withoutFirst); // ["b", "c", "d", "e"]
// Remove last element (immutable pop)
const withoutLast = items.slice(0, -1);
console.log(withoutLast); // ["a", "b", "c", "d"]
// Remove first 2 and last 2
const middle = items.slice(2, -2);
console.log(middle); // ["c"]
// Original unchanged in all cases
console.log(items); // ["a", "b", "c", "d", "e"]Capping Array Size
Keeping only the most recent N items without mutation:
function keepRecent(items, maxCount) {
if (items.length <= maxCount) return items;
return items.slice(-maxCount);
}
const logs = ["event1", "event2", "event3", "event4", "event5", "event6"];
const recent = keepRecent(logs, 3);
console.log(recent); // ["event4", "event5", "event6"]Converting Arguments to a Real Array
Before rest parameters existed, slice was the standard way to convert the arguments object into a real array:
// Legacy pattern (pre-ES6)
function legacy() {
const args = Array.prototype.slice.call(arguments);
return args.map(a => a.toUpperCase());
}
// Modern equivalent
function modern(...args) {
return args.map(a => a.toUpperCase());
}
console.log(legacy("hello", "world")); // ["HELLO", "WORLD"]
console.log(modern("hello", "world")); // ["HELLO", "WORLD"]slice() vs splice(): The Critical Difference
These two methods have similar names but completely different behavior. This confusion is one of the most common sources of bugs for JavaScript developers. A dedicated comparison is covered in the slice vs splice article, but here is a quick reference:
| Feature | slice() | splice() |
|---|---|---|
| Mutates original | No | Yes |
| Purpose | Extract a portion | Add, remove, or replace elements |
| Returns | New array (extracted portion) | Array of removed elements |
| Parameters | (start, end) | (start, deleteCount, ...items) |
| End parameter | Exclusive (not included) | N/A (uses count, not end index) |
const arr = [1, 2, 3, 4, 5];
// slice: non-destructive extraction
const sliced = arr.slice(1, 3);
console.log(sliced); // [2, 3]
console.log(arr); // [1, 2, 3, 4, 5] — unchanged
// splice: destructive modification
const spliced = arr.splice(1, 2);
console.log(spliced); // [2, 3]
console.log(arr); // [1, 4, 5] — modified!slice() with Strings
JavaScript strings also have a slice() method with identical parameter behavior:
const text = "JavaScript";
console.log(text.slice(0, 4)); // "Java"
console.log(text.slice(4)); // "Script"
console.log(text.slice(-6)); // "Script"The consistency between string and array slice() makes it easy to remember: start inclusive, end exclusive, negatives count from the end.
Performance
slice() runs in O(k) time where k is the number of elements being copied. For a full clone, that is O(n). The operation is a single memory allocation plus a shallow copy, making it fast for typical array sizes:
// Slicing 10,000 elements from a 1M array: ~0.1ms
const huge = Array.from({ length: 1_000_000 }, (_, i) => i);
console.time("slice-10k");
const portion = huge.slice(0, 10_000);
console.timeEnd("slice-10k"); // ~0.05-0.15msCommon Mistakes
Forgetting that end is exclusive:
const arr = [10, 20, 30, 40, 50];
// Bug: developer wants elements at indices 1, 2, 3
const wrong = arr.slice(1, 3); // Only gets indices 1, 2
console.log(wrong); // [20, 30] — missing 40!
// Fix: add 1 to end parameter
const right = arr.slice(1, 4);
console.log(right); // [20, 30, 40]Assuming slice creates a deep copy:
const items = [{ count: 1 }, { count: 2 }];
const copy = items.slice();
copy[0].count = 99;
console.log(items[0].count); // 99 — both reference the same objectRune AI
Key Insights
- Non-mutating:
slice()never changes the original array; it always returns a new one - Inclusive start, exclusive end:
slice(1, 4)includes indices 1, 2, and 3, not 4 - Negative indices: Count from the end;
slice(-3)returns the last three elements - Shallow copy: Object references are shared between the original and sliced arrays
- Versatile patterns: Use
slice()for pagination, immutable removal, cloning, and capping array size
Frequently Asked Questions
Does slice() modify the original array?
What is the difference between slice() and splice()?
How do I copy an array with slice()?
Can I use slice() with negative numbers?
Is slice() or spread faster for copying arrays?
Conclusion
The slice() method is the backbone of non-mutating array operations in JavaScript. It extracts any contiguous portion of an array by start and end index, supports negative indices for counting from the end, and always returns a new array. Its non-destructive nature makes it essential for React state updates, functional programming patterns, pagination, and any scenario where the original data must remain intact. The most critical detail to internalize is that end is exclusive, and that the copy is shallow when the array contains objects.
More in this topic
OffscreenCanvas API in JS for UI Performance
Master the OffscreenCanvas API to offload rendering from the main thread. Covers worker-based 2D and WebGL rendering, animation loops inside workers, bitmap transfer, double buffering, chart rendering pipelines, image processing, and performance measurement strategies.
Advanced Web Workers for High Performance JS
Master Web Workers for truly parallel JavaScript execution. Covers dedicated and shared workers, structured cloning, transferable objects, SharedArrayBuffer with Atomics, worker pools, task scheduling, Comlink RPC patterns, module workers, and performance profiling strategies.
JavaScript Macros and Abstract Code Generation
Master JavaScript code generation techniques for compile-time and runtime metaprogramming. Covers AST manipulation, Babel plugin authorship, tagged template literals as macros, code generation pipelines, source-to-source transformation, compile-time evaluation, and safe eval alternatives.