TypeScript Array slice(): Negative Indexes and Examples

How slice(start, end) copies part of a TypeScript array: excluded end index, negative indexes, pagination and chunks, shallow copies, slice() vs splice(), and result types.

The TypeScript array slice() method returns a new array with the elements from a start index up to, but not including, an end index. It never changes the original array.

We use slice() to take the first or last few elements, cut a page out of a long list, split an array into chunks or copy the whole array. The method comes from JavaScript and is documented on MDN, and TypeScript adds only the types, so on a string[] it returns a string[].

The following example shows every form of slice() that we use day to day, with the result next to each call. It uses TypeScript 7.0 with strict enabled and Node.js 22.

const letters = ["a", "b", "c", "d", "e", "f", "g"];

// 1. From an index to the end
const fromThree = letters.slice(3);           // fromThree = ["d", "e", "f", "g"]

// 2. From start (included) to end (excluded)
const middle = letters.slice(2, 5);           // middle = ["c", "d", "e"]

// 3. Negative indexes count from the end
const lastTwo = letters.slice(-2);            // lastTwo = ["f", "g"]
const trimmed = letters.slice(1, -1);         // trimmed = ["b", "c", "d", "e", "f"]

// 4. First n elements
const firstThree = letters.slice(0, 3);       // firstThree = ["a", "b", "c"]

// 5. Copy of the whole array
const copy = letters.slice();                 // copy = all 7 letters, a new array

// letters is unchanged

Notice that letters stays the same after all the calls, because each call returns a new array.

Most mistakes with slice() come from three details, which the next sections explain with examples.

  • The end index is excluded.
  • Negative indexes count from the end.
  • The copy is shallow.

After that, we compare slice() with splice() and look at how slice() types readonly arrays and tuples.

1. Syntax: start Is Included, end Is Not

TypeScript declares the method in its library file lib.es5.d.ts as slice(start?: number, end?: number): T[], where T is the element type. Both arguments are optional zero-based indexes, and the result is always an array of the same element type.

ArgumentMeaningDefault
startFirst index to copy (included)0
endIndex to stop at (excluded)array.length

Because the end is excluded, the number of copied elements is end – start whenever both are inside the array. That rule makes slice(0, n) return n elements and lets two slices meet without overlap, so slice(0, 3) and slice(3) together contain every element once.

const nums = [10, 20, 30, 40, 50];

const part = nums.slice(1, 4);                // part = [20, 30, 40]
const count = part.length;                    // count = 3, that is 4 - 1
const all = nums.slice();                     // all = [10, 20, 30, 40, 50]
const same = all === nums;                    // same = false, a new array

Calling slice() without arguments copies the whole array. The spread syntax […nums] does the same, and both are common in code that must not change its input.

2. Negative Indexes

A negative index counts from the end of the array, so -1 is the last element and -2 the one before it. Internally, slice() adds array.length to a negative value, so for a 7-element array, -3 becomes index 4. The include/exclude rule stays the same, as the diagram shows.

Diagram of a 7-letter array with indexes 0 to 6 above and -7 to -1 below, highlighting slice(2, 5) and slice(-3, -1)
Negative indexes are converted to length + index, then the same rule applies: start is included, end is not.
const letters = ["a", "b", "c", "d", "e", "f", "g"];

const lastThree = letters.slice(-3);          // lastThree = ["e", "f", "g"]
const range = letters.slice(-3, -1);          // range = ["e", "f"]
const mixed = letters.slice(2, -2);           // mixed = ["c", "d", "e"]
const empty = letters.slice(-1, -3);          // empty = [], start is after end

The calls slice(1, -1) and slice(2, -2) are handy for dropping elements from both ends at once, for example the header and footer rows of a table. When start ends up at or after end, the result is an empty array, not an error.

3. Pagination, Chunks and the Last n Elements

Most real uses of slice() compute the indexes from other values. Pagination is the typical case, where page p of size s starts at (p – 1) * s and ends before p * s. For example, an online shop that shows 20 products per page takes page 3 with slice(40, 60). Chunking repeats the same calculation in a loop.

const nums = [1, 2, 3, 4, 5, 6, 7];

// 1. Pagination: page 2 with 3 items per page
const page = 2;
const size = 3;
const items = nums.slice((page - 1) * size, page * size);   // items = [4, 5, 6]

// 2. Split into chunks of 3
const chunks: number[][] = [];
for (let i = 0; i < nums.length; i += size) {
  chunks.push(nums.slice(i, i + size));
}
// chunks = [[1, 2, 3], [4, 5, 6], [7]]

// 3. Last n elements: slice(-n) fails for n = 0
const n = 0;
const wrong = nums.slice(-n);                 // wrong = all 7 elements
const right = nums.slice(nums.length - n);    // right = []

The last chunk can be shorter than the others; slice() stops at the end of the array without an error. Step 3 shows a bug that hides in “last n” helpers. When n is 0, -n is -0, which slice() treats as 0, so the call returns the whole array instead of nothing. Writing the start as nums.length – n avoids the bug.

4. Out-of-Range and Unusual Arguments

The slice() method never throws for a bad index, because it clamps the values to the array bounds instead, which is convenient for pagination but can hide a wrong calculation.

const letters = ["a", "b", "c"];

const past = letters.slice(5);                // past = []
const clamped = letters.slice(1, 99);         // clamped = ["b", "c"]
const fromStart = letters.slice(-99, 1);      // fromStart = ["a"]
const decimal = letters.slice(1.7);           // decimal = ["b", "c"], 1.7 becomes 1

Each kind of unusual argument has a fixed result.

  • A start at or past the length gives an empty array.
  • An end past the length stops at the last element.
  • A negative value larger than the length starts at 0.
  • A decimal number is truncated toward zero.

When the source array has empty slots (a sparse array), the copy keeps them as empty slots.

5. slice() Makes a Shallow Copy

The array that slice() returns is new, but the elements are copied as they are. Strings, numbers and booleans are values, so changing them in the copy cannot affect the original. Objects are copied by reference, so both arrays point to the same objects, and changing a property through the copy changes the original too. For example, a game takes the top three players from the scoreboard with slice(), and a score change made through that slice also changes the scoreboard.

const people = [
  { name: "Lokesh", age: 37 },
  { name: "Raj", age: 35 },
];

const firstPerson = people.slice(0, 1);
firstPerson[0].age = 38;                      // people[0].age = 38 too

const firstClone = structuredClone(people.slice(0, 1));
firstClone[0].age = 40;                       // people[0].age stays 38

When the copied objects will be changed, structuredClone() creates independent copies. It costs more time and memory than a shallow copy and cannot copy functions or class instances with their methods, so we use it only when we need it. Replacing whole elements in the copy, for example firstPerson[0] = { name: “Amit”, age: 30 }, does not affect the original either.

6. slice() vs splice()

The names differ by one letter, so we mix up the two methods often. The slice() method copies a part and leaves the array alone, whereas splice() removes a part from the array and returns it, and it can also insert new elements. Their second arguments also mean different things, because slice() takes an end index, whereas splice() takes a count.

const a = [1, 2, 3, 4, 5];
const b = [1, 2, 3, 4, 5];

const sliced = a.slice(1, 3);                 // sliced = [2, 3], a = [1, 2, 3, 4, 5]
const spliced = b.splice(1, 3);               // spliced = [2, 3, 4], b = [1, 5]
slice(start, end)splice(start, deleteCount, …items)
Changes the original arrayNoYes
Second argumentEnd index (excluded)Number of elements to remove
ReturnsThe selected elementsThe removed elements
Can insert elementsNoYes
Works on readonly arraysYesNo

When we need the result of splice() without changing the array, ES2023 added toSpliced(), which returns a changed copy. We use splice() or toSpliced() to remove items from an array or to add items at a given position.

7. How TypeScript Types the Result

The return type of slice() is always a regular mutable array of the source element type. The mutable return type has two practical effects, one helpful and one that loses information.

// 1. readonly in, mutable copy out
const days: readonly string[] = ["Mon", "Tue"];
const copy = days.slice();                    // string[]
copy.push("Wed");                             // copy = ["Mon", "Tue", "Wed"]

// 2. Tuples lose their positions
const person: [string, number, boolean] = ["Lokesh", 37, true];
const part = person.slice(0, 2);              // (string | number | boolean)[]

// 3. Strings have slice() too
const word = "hello".slice(1, -1);            // word = "ell"

On a readonly array, slice() is the usual way to get a copy we are allowed to change. On a tuple, the compiler does not track which positions were copied, so part is an array of the union of all element types, and part[1] is no longer known to be a number. To keep the exact types, we use destructuring instead, as in const [name, age] = person. Strings have their own slice() method with the same start and end rules.

8. Source Code

The typescript-array-slice-examples project on GitHub has one file per section in src, and npm start compiles the project and prints each result. The project pins TypeScript 7.0.2 and runs on Node.js 22 or later.

npm install
npm start

9. Conclusion

The call slice(start, end) copies the elements from start up to, but not including, end into a new array, and leaves the original unchanged. Negative indexes count from the end, and out-of-range values are clamped without errors. A call without arguments copies the whole array. The copy is shallow, so objects stay shared with the source. When the goal is to change the array itself, splice() is the method to use; when only a copy is needed, slice() is the safer choice, and it also works on readonly arrays.

10. References

MDN describes the exact index conversion rules, and the TypeScript library file lib.es5.d.ts declares the slice() signature.

Happy Learning !!

Source Code on Github

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.