How to Split an Array in TypeScript

To split an array in TypeScript, use slice(): arr.slice(0, i) gives the part before index i and arr.slice(i) gives the rest, without changing the original. To split into chunks of N items, call slice in a loop:

const firstHalf = months.slice(0, 3);
const rest = months.slice(3);

For the outputs on this page I used TypeScript 7.0.2 on Node v22.22.2 with strict checks, so the types you see are what the compiler inferred.

You’ll see splitting at an index or in half, chunks of a fixed size, splitting by a condition with correct types, splitting into many groups, and splitting at separator values.

Split an array at an index or in half with slice()

slice(start, end) copies the elements from start up to, but not including, end. Two calls give you both halves:

const months: string[] = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul"];

const firstHalf = months.slice(0, 3);
const rest = months.slice(3);
console.log(firstHalf, rest);

const mid = Math.ceil(months.length / 2);           // odd length: first half gets the extra
console.log(months.slice(0, mid), months.slice(mid));

const [head, ...tail] = months;                     // first item + the rest
console.log(head, tail.length);
console.log("original:", months.length, "items, unchanged");

Output:

[ 'Jan', 'Feb', 'Mar' ] [ 'Apr', 'May', 'Jun', 'Jul' ]
[ 'Jan', 'Feb', 'Mar', 'Apr' ] [ 'May', 'Jun', 'Jul' ]
Jan 6
original: 7 items, unchanged
Command Prompt output of TypeScript slice splitting a months array at an index and in half, plus head and tail destructuring
The months split after March, then in half with the first part getting the extra month, and the original array still has seven items.

Math.ceil decides which half gets the odd element. If you’d rather cut items out of the original array, that’s a job for the TypeScript array splice method.

How do I split an array into chunks in TypeScript?

There’s no built-in chunk method, but a generic helper takes a few lines. It’s handy for batching API calls or paging:

function chunk<T>(items: readonly T[], size: number): T[][] {
  if (!Number.isInteger(size) || size < 1) throw new RangeError(`size must be a positive integer, got ${size}`);
  const out: T[][] = [];
  for (let i = 0; i < items.length; i += size) {
    out.push(items.slice(i, i + size));
  }
  return out;
}

const emails = ["a@x.com", "b@x.com", "c@x.com", "d@x.com", "e@x.com", "f@x.com", "g@x.com"];
const batches = chunk(emails, 3);
batches.forEach((b, i) => console.log(`batch ${i + 1}:`, b));

console.log(chunk([1, 2, 3], 5));
console.log(chunk([], 2));
try {
  chunk(emails, 0);
} catch (err) {
  console.log((err as Error).name + ":", (err as Error).message);
}

Output:

batch 1: [ 'a@x.com', 'b@x.com', 'c@x.com' ]
batch 2: [ 'd@x.com', 'e@x.com', 'f@x.com' ]
batch 3: [ 'g@x.com' ]
[ [ 1, 2, 3 ] ]
[]
RangeError: size must be a positive integer, got 0
Command Prompt output of a TypeScript generic chunk function splitting seven emails into batches of three and rejecting size 0
Seven emails become batches of three, three and one; a size of 0 throws a RangeError instead of looping forever.

Accepting readonly T[] lets you pass readonly arrays as well. The last chunk is simply shorter when the length isn’t a multiple of the size.

Split an array into two groups by a condition

A partition function returns a tuple of matches and non-matches. With an overload for type guards, TypeScript also narrows the types of both halves:

function partition<T, S extends T>(items: readonly T[], test: (item: T) => item is S): [S[], Exclude<T, S>[]];
function partition<T>(items: readonly T[], test: (item: T) => boolean): [T[], T[]];
function partition<T>(items: readonly T[], test: (item: T) => boolean): [T[], T[]] {
  const pass: T[] = [];
  const fail: T[] = [];
  for (const item of items) (test(item) ? pass : fail).push(item);
  return [pass, fail];
}

const totals = [129.99, 54.5, 210.0, 15.25, 99.0];
const [freeShipping, paidShipping] = partition(totals, (t) => t >= 100);
console.log("free shipping:", freeShipping, "| paid:", paidShipping);

const mixed: (string | number)[] = ["TX", 75001, "CA", 90210];
const [states, zips] = partition(mixed, (v): v is string => typeof v === "string");
console.log(states.map((s) => s.toLowerCase()), zips.map((z) => z + 1));   // typed as string[] and number[]

Output:

free shipping: [ 129.99, 210 ] | paid: [ 54.5, 15.25, 99 ]
[ 'tx', 'ca' ] [ 75002, 90211 ]
Command Prompt output of a TypeScript partition function splitting order totals by a threshold and mixed values into strings and numbers
Totals of $100 or more land in the free-shipping group, and the mixed array splits into state codes and ZIP numbers with correct types.

Two filter() calls would work but walk the array twice. More filtering patterns are in filtering arrays in TypeScript.

Split an array into many groups with Object.groupBy()

When there are more than two buckets, Object.groupBy() (ES2024) splits by any key you return:

interface Ticket { id: number; priority: "high" | "medium" | "low" }

const tickets: Ticket[] = [
  { id: 1, priority: "high" }, { id: 2, priority: "low" }, { id: 3, priority: "medium" },
  { id: 4, priority: "high" }, { id: 5, priority: "low" },
];

const byPriority = Object.groupBy(tickets, (t) => t.priority);
console.log("high:", byPriority.high?.map((t) => t.id));
console.log("medium:", byPriority.medium?.map((t) => t.id));
console.log("low:", byPriority.low?.map((t) => t.id));

Output:

high: [ 1, 4 ]
medium: [ 3 ]
low: [ 2, 5 ]

Each group is optional in the type, so use ?. when reading it. For a custom shape, build the groups with the array reduce method instead.

Split an array at separator values

Sometimes the split points are marker items, such as blank lines between addresses. Start a new group whenever you see one:

function splitOn<T>(items: readonly T[], isSeparator: (item: T) => boolean): T[][] {
  const groups: T[][] = [[]];
  for (const item of items) {
    if (isSeparator(item)) groups.push([]);
    else groups[groups.length - 1].push(item);
  }
  return groups.filter((g) => g.length > 0);
}

const lines = ["Emily Carter", "Austin, TX", "", "James Wilson", "Denver, CO", "", "", "Olivia Brown", "Tampa, FL"];
const addresses = splitOn(lines, (line) => line.trim() === "");
console.log(addresses);

Output:

[
  [ 'Emily Carter', 'Austin, TX' ],
  [ 'James Wilson', 'Denver, CO' ],
  [ 'Olivia Brown', 'Tampa, FL' ]
]
Command Prompt output of a TypeScript splitOn function grouping lines into addresses separated by blank lines
The lines become three address groups, and the two blank lines in a row don’t create an empty group.

Filtering out empty groups handles repeated separators. If your input is one long string instead, start with splitting a string by comma in TypeScript.

Ways to split an array compared

GoalUseMutates?
At an indexslice(0, i) + slice(i)No
In halfMath.ceil(len / 2) + sliceNo
Chunks of Nchunk(arr, n) loopNo
Two groups by conditionpartition()No
Many groupsObject.groupBy()No
Remove a sectionsplice()Yes

These array tutorials pair nicely with splitting:

Frequently asked questions

How do I split an array into two in TypeScript?

Use arr.slice(0, i) and arr.slice(i). Neither changes the original array.

How do I split an array into chunks of a given size?

Loop with i += size and push arr.slice(i, i + size) into a result array.

How do I split an array by a condition?

Use a partition helper that returns [matches, rest], or two filter() calls.

How do I keep types when splitting mixed arrays?

Pass a type-guard function like (v): v is string => typeof v === "string" to a partition overload.

What is the difference between slice and splice for splitting?

slice copies part of the array; splice removes that part from the original.

Where is Array.prototype.slice documented?